Skip to content

Commit 8732270

Browse files
authored
Merge pull request #51 from plotly/0.0.2-issue11984
Refactor DashR callbacks for increased parity with Dash for Python API
2 parents 7663d58 + 6cf95ff commit 8732270

12 files changed

+36015
-206
lines changed

DESCRIPTION

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
Package: dashR
22
Title: An interface to the dash ecosystem for authoring reactive web applications
3-
Version: 0.0.1
3+
Version: 0.0.2
44
Authors@R: c(person("Ryan", "Kyle", role = c("aut", "cre"), email = "[email protected]"), person("Carson", "Sievert", role = c("aut")))
55
Description: An interface to the dash ecosystem for authoring reactive web applications.
66
Depends:
7-
R (>= 3.5)
7+
R (>= 3.4)
88
Imports:
99
R6,
1010
fiery (> 1.0.0),

NAMESPACE

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
S3method(print,dash_component)
44
export(Dash)
55
export(dash_css)
6-
export(event)
76
export(heroku_app_deploy)
87
export(heroku_app_template)
98
export(input)

R/dash.R

+39-101
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ Dash <- R6::R6Class(
180180

181181
dash_index <- routes_pathname_prefix
182182
route$add_handler("get", dash_index, function(request, response, keys, ...) {
183+
183184
response$body <- private$.index
184185
response$status <- 200L
185186
response$type <- 'html'
@@ -188,6 +189,7 @@ Dash <- R6::R6Class(
188189

189190
dash_layout <- paste0(routes_pathname_prefix, "_dash-layout")
190191
route$add_handler("get", dash_layout, function(request, response, keys, ...) {
192+
191193
lay <- private$layout_render()
192194
response$body <- to_JSON(lay, pretty = TRUE)
193195
response$status <- 200L
@@ -196,6 +198,7 @@ Dash <- R6::R6Class(
196198
})
197199

198200
dash_deps <- paste0(routes_pathname_prefix, "_dash-dependencies")
201+
199202
route$add_handler("get", dash_deps, function(request, response, keys, ...) {
200203

201204
# dash-renderer wants an empty array when no dependencies exist (see python/01.py)
@@ -206,19 +209,13 @@ Dash <- R6::R6Class(
206209
return(FALSE)
207210
}
208211

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) {
214213
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
220217
)
221-
}, private$callback_map, outputs)
218+
}, private$callback_map)
222219

223220
response$body <- to_JSON(setNames(payload, NULL))
224221
response$status <- 200L
@@ -228,7 +225,6 @@ Dash <- R6::R6Class(
228225

229226
dash_update <- paste0(routes_pathname_prefix, "_dash-update-component")
230227
route$add_handler("post", dash_update, function(request, response, keys, ...) {
231-
232228
request <- request_parse_json(request)
233229

234230
if (!"output" %in% names(request$body)) {
@@ -240,41 +236,14 @@ Dash <- R6::R6Class(
240236

241237
# get the callback associated with this particular output
242238
thisOutput <- with(request$body$output, paste(id, property, sep = "."))
243-
callback <- private$callback_map[[thisOutput]]
239+
callback <- private$callback_map[[thisOutput]][['func']]
244240
if (!length(callback)) stop_report("Couldn't find output component.")
245241
if (!is.function(callback)) {
246242
stop(sprintf("Couldn't find a callback function associated with '%s'", thisOutput))
247243
}
248244

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)
278247

279248
# have to format the response body like this
280249
# https://github.com/plotly/dash/blob/064c811d/dash/dash.py#L562-L584
@@ -295,6 +264,7 @@ Dash <- R6::R6Class(
295264
# https://plotly.slack.com/archives/D07PDTRK6/p1507657249000714?thread_ts=1505157408.000123&cid=D07PDTRK6
296265
dash_suite <- paste0(routes_pathname_prefix, "_dash-component-suites")
297266
route$add_handler("get", dash_suite, function(request, response, keys, ...) {
267+
298268
response$status <- 500L
299269
response$body <- "Not yet implemented"
300270
TRUE
@@ -320,6 +290,8 @@ Dash <- R6::R6Class(
320290
},
321291
layout_set = function(...) {
322292
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())
323295
},
324296

325297
# ------------------------------------------------------------------------
@@ -366,55 +338,21 @@ Dash <- R6::R6Class(
366338
# ------------------------------------------------------------------------
367339
# callback registration
368340
# ------------------------------------------------------------------------
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
400353
)
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
415354
},
416355

417-
418356
# ------------------------------------------------------------------------
419357
# convenient fiery wrappers
420358
# ------------------------------------------------------------------------
@@ -443,7 +381,7 @@ Dash <- R6::R6Class(
443381
dependencies = list(),
444382
dependencies_user = list(),
445383
dependencies_internal = list(),
446-
384+
447385
# layout stuff
448386
layout = welcome_page(),
449387
layout_ids = NULL,
@@ -505,27 +443,27 @@ Dash <- R6::R6Class(
505443
# construct function name based on package name
506444
fn_name <- paste0(".", pkg, "_js_metadata")
507445
fn_summary <- getAnywhere(fn_name)
508-
446+
509447
# ensure that the object refers to a function,
510448
# and we are able to locate it somewhere
511449
if (length(fn_summary$where) == 0) return(NULL)
512-
450+
513451
if (mode(fn_summary$obj[[1]]) == "function") {
514452
# function is available
515453
dep_list <- do.call(fn_summary$obj[[1]], list())
516454

517455
return(dep_list)
518456
} else {
519-
return(NULL)
457+
return(NULL)
520458
}
521459
})
522-
460+
523461
deps_layout <- unlist(deps_layout, recursive=FALSE)
524462

525463
# add on HTML dependencies we've identified by crawling the layout
526464
private$dependencies <- c(private$dependencies, deps_layout)
527465

528-
# DashR's own dependencies
466+
# DashR's own dependencies
529467
private$dependencies_internal <- dashR:::.dashR_js_metadata()
530468

531469
# return the computed layout
@@ -572,6 +510,7 @@ Dash <- R6::R6Class(
572510
# akin to https://github.com/plotly/dash-renderer/blob/master/dash_renderer/__init__.py
573511
react_version_enabled= function() {
574512
version <- private$dependencies_internal$react$version
513+
return(version)
575514
},
576515
react_deps = function() {
577516
deps <- private$dependencies_internal
@@ -580,11 +519,13 @@ Dash <- R6::R6Class(
580519
react_versions = function() {
581520
vapply(private$react_deps(), "[[", character(1), "version")
582521
},
583-
522+
584523
# akin to https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L338
585524
# note discussion here https://github.com/plotly/dash/blob/d2ebc837/dash/dash.py#L279-L284
586525
.index = NULL,
587526
index = function() {
527+
528+
588529
# collect and resolve dependencies
589530
depsAll <- compact(c(
590531
private$react_deps()[private$react_versions() %in% private$react_version_enabled()],
@@ -643,9 +584,6 @@ Dash <- R6::R6Class(
643584
)
644585
)
645586

646-
647-
648-
649587
# verify that properties attached to output/inputs/state value are valid
650588
# @param layout
651589
# @param component a component (should be a dependency)
@@ -669,16 +607,16 @@ validate_dependency <- function(layout, dependency) {
669607
TRUE
670608
}
671609

672-
assert_valid_wildcards <- function (...)
610+
assert_valid_wildcards <- function (...)
673611
{
674612
args <- list(...)
675613
validation_results <- lapply(args, function(x) {
676614
grepl(c('^data-[a-zA-Z0-9]{1,}$|^aria-[a-zA-Z0-9]{1,}$'), x)
677615
}
678616
)
679617
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))],
682620
collapse=", ")), call. = FALSE)
683621
} else {
684622
return(args)

R/dependencies.R

+4-14
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
#' @rdname dependencies
1515
#' @export
16-
output <- function(id = NULL, property = "children") {
16+
output <- function(id, property) {
1717
structure(
1818
dependency(id, property),
1919
class = c("dash_dependency", "output")
@@ -22,7 +22,7 @@ output <- function(id = NULL, property = "children") {
2222

2323
#' @rdname dependencies
2424
#' @export
25-
input <- function(id = NULL, property = "value") {
25+
input <- function(id, property) {
2626
structure(
2727
dependency(id, property),
2828
class = c("dash_dependency", "input")
@@ -31,27 +31,17 @@ input <- function(id = NULL, property = "value") {
3131

3232
#' @rdname dependencies
3333
#' @export
34-
state <- function(id = NULL, property = "value") {
34+
state <- function(id, property) {
3535
structure(
3636
dependency(id, property),
3737
class = c("dash_dependency", "state")
3838
)
3939
}
4040

41-
#' @rdname dependencies
42-
#' @export
43-
event <- function(id = NULL, property = "value") {
44-
structure(
45-
dependency(id, property),
46-
class = c("dash_dependency", "event")
47-
)
48-
}
49-
5041
dependency <- function(id = NULL, property = NULL) {
5142
if (is.null(id)) stop("Must specify an id", call. = FALSE)
5243
list(
5344
id = id,
54-
property = property,
55-
key = paste0(id, ".", property)
45+
property = property
5646
)
5747
}

R/internal.R

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@
4848
all_files = FALSE),
4949
class = "html_dependency"),
5050
`dash-renderer` = structure(list(name = "dash-renderer",
51-
version = "0.13.0",
52-
src = list(href = "https://unpkg.com/dash-renderer@0.13.0",
53-
file = "lib/dash-renderer@0.13.0"),
51+
version = "0.18.0",
52+
src = list(href = "https://unpkg.com/dash-renderer@0.18.0",
53+
file = "lib/dash-renderer@0.18.0"),
5454
meta = NULL,
55-
script = "dash_renderer/bundle.js",
55+
script = "dash_renderer/dash_renderer.min.js",
5656
stylesheet = NULL,
5757
head = NULL,
5858
attachment = NULL,

0 commit comments

Comments
 (0)