Skip to content

Commit 5c83f5c

Browse files
authored
Use dev_tools_prune_errors instead of pruned_errors (#113)
* rename pruned_errors to prune_errors * rename pruned to prune * provide support for no_update in Dash for R (#111)
1 parent fcfedcb commit 5c83f5c

File tree

6 files changed

+144
-9
lines changed

6 files changed

+144
-9
lines changed

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
S3method(print,dash_component)
44
export(Dash)
5+
export(dashNoUpdate)
56
export(input)
67
export(output)
78
export(state)

R/dash.R

+12-6
Original file line numberDiff line numberDiff line change
@@ -286,12 +286,18 @@ Dash <- R6::R6Class(
286286

287287
output_value <- getStackTrace(do.call(callback, callback_args),
288288
debug = private$debug,
289-
pruned_errors = private$pruned_errors)
290-
289+
prune_errors = private$prune_errors)
290+
291291
# reset callback context
292292
private$callback_context_ <- NULL
293293

294-
if (is.null(private$stack_message)) {
294+
# inspect the output_value to determine whether any outputs have no_update
295+
# objects within them; these should not be updated
296+
if (length(output_value) == 1 && class(output_value) == "no_update") {
297+
response$body <- character(1) # return empty string
298+
response$status <- 204L
299+
}
300+
else if (is.null(private$stack_message)) {
295301
# pass on output_value to encode_plotly in case there are dccGraph
296302
# components which include Plotly.js figures for which we'll need to
297303
# run plotly_build from the plotly package
@@ -525,7 +531,7 @@ Dash <- R6::R6Class(
525531
port = Sys.getenv('DASH_PORT', 8050),
526532
block = TRUE,
527533
showcase = FALSE,
528-
pruned_errors = TRUE,
534+
dev_tools_prune_errors = TRUE,
529535
debug = FALSE,
530536
dev_tools_ui = NULL,
531537
dev_tools_props_check = NULL,
@@ -545,7 +551,7 @@ Dash <- R6::R6Class(
545551
self$config$props_check <- FALSE
546552
}
547553

548-
private$pruned_errors <- pruned_errors
554+
private$prune_errors <- dev_tools_prune_errors
549555
private$debug <- debug
550556

551557
self$server$ignite(block = block, showcase = showcase, ...)
@@ -569,7 +575,7 @@ Dash <- R6::R6Class(
569575

570576
# initialize flags for debug mode and stack pruning,
571577
debug = NULL,
572-
pruned_errors = NULL,
578+
prune_errors = NULL,
573579
stack_message = NULL,
574580

575581
# callback context

R/dependencies.R

+11-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
#' Use in conjunction with the `callback()` method from the [dash::Dash] class
66
#' to define the update logic in your application.
77
#'
8+
#' The `dashNoUpdate()` function permits application developers to prevent a
9+
#' single output from updating the layout. It has no formal arguments.
10+
#'
811
#' @name dependencies
912
#' @param id a component id
1013
#' @param property the component property to use
1114

12-
1315
#' @rdname dependencies
1416
#' @export
1517
output <- function(id, property) {
@@ -44,3 +46,11 @@ dependency <- function(id = NULL, property = NULL) {
4446
property = property
4547
)
4648
}
49+
50+
#' @rdname dependencies
51+
#' @export
52+
dashNoUpdate <- function() {
53+
x <- list(NULL)
54+
class(x) <- "no_update"
55+
return(x)
56+
}

R/utils.R

+2-2
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ stackTraceToHTML <- function(call_stack,
685685
# and capture the call stack. By default, the call
686686
# stack will be "pruned" of error handling functions
687687
# for greater readability.
688-
getStackTrace <- function(expr, debug = FALSE, pruned_errors = TRUE) {
688+
getStackTrace <- function(expr, debug = FALSE, prune_errors = TRUE) {
689689
if(debug) {
690690
tryCatch(withCallingHandlers(
691691
expr,
@@ -711,7 +711,7 @@ getStackTrace <- function(expr, debug = FALSE, pruned_errors = TRUE) {
711711

712712
reverseStack <- rev(calls)
713713

714-
if (pruned_errors) {
714+
if (prune_errors) {
715715
# this line should match the last occurrence of the function
716716
# which raised the error within the call stack; prune here
717717
indexFromLast <- match(TRUE, lapply(reverseStack, function(currentCall) {

man/dependencies.Rd

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from selenium.webdriver.support.select import Select
2+
import time
3+
4+
app = """
5+
library(dash)
6+
library(dashHtmlComponents)
7+
library(dashCoreComponents)
8+
9+
app <- Dash$new()
10+
11+
app$layout(
12+
htmlDiv(list(
13+
dccDropdown(options = list(
14+
list(label = "Red", value = "#FF0000"),
15+
list(label = "Green", value = "#00FF00"),
16+
list(label = "Blue", value = "#0000FF"),
17+
list(label = "Do nothing", value = "nothing")
18+
),
19+
id = "color-selector"),
20+
htmlButton(children = "Select all the colors!",
21+
id = "multi-selector"
22+
),
23+
htmlDiv(id='message-box',
24+
children='Please select a color choice from the dropdown menu.'),
25+
htmlDiv(id='message-box2',
26+
children=' ')
27+
)
28+
)
29+
)
30+
31+
app$callback(output=list(id='message-box2', property='children'),
32+
params=list(
33+
input(id='multi-selector', property='n_clicks')),
34+
function(n_clicks)
35+
{
36+
# if button has been clicked, n_clicks is numeric()
37+
# on first launch of callback at layout initialization,
38+
# value of n_clicks will be list(NULL), which is not
39+
# comparable using >, < or =; hence the is.numeric()
40+
# check
41+
if (is.numeric(n_clicks) && n_clicks >= 1)
42+
{
43+
# return a vector to ensure that the check for
44+
# class(x) == "no_update" isn't made for objects
45+
# where length(x) > 1
46+
return(c("Multiple color values: ",
47+
"#FF0000, ",
48+
"#00FF00, ",
49+
"#0000FF ",
50+
"returned!")
51+
)
52+
}
53+
}
54+
)
55+
56+
app$callback(output=list(id='message-box', property='children'),
57+
params=list(
58+
input(id='color-selector', property='value')),
59+
function(color)
60+
{
61+
if (color %in% c("#FF0000", "#00FF00", "#0000FF")) {
62+
msg <- sprintf("The hexadecimal representation of your last chosen color is %s",
63+
color)
64+
return(msg)
65+
} else {
66+
return(dashNoUpdate())
67+
}
68+
}
69+
)
70+
71+
app$run_server()
72+
"""
73+
74+
75+
def test_rsnu001_no_update(dashr):
76+
dashr.start_server(app)
77+
dashr.find_element("#color-selector").click()
78+
dashr.find_elements("div.VirtualizedSelectOption")[0].click()
79+
dashr.wait_for_text_to_equal(
80+
"#message-box",
81+
"The hexadecimal representation of your last chosen color is #FF0000"
82+
)
83+
dashr.find_element("#color-selector").click()
84+
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
85+
time.sleep(1)
86+
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #FF0000"
87+
dashr.find_element("#color-selector").click()
88+
dashr.find_elements("div.VirtualizedSelectOption")[1].click()
89+
dashr.wait_for_text_to_equal(
90+
"#message-box",
91+
"The hexadecimal representation of your last chosen color is #00FF00"
92+
)
93+
dashr.find_element("#color-selector").click()
94+
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
95+
time.sleep(1)
96+
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #00FF00"
97+
dashr.find_element("#color-selector").click()
98+
dashr.find_elements("div.VirtualizedSelectOption")[2].click()
99+
dashr.wait_for_text_to_equal(
100+
"#message-box",
101+
"The hexadecimal representation of your last chosen color is #0000FF"
102+
)
103+
dashr.find_element("#color-selector").click()
104+
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
105+
time.sleep(1)
106+
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #0000FF"
107+
dashr.find_element("#multi-selector").click()
108+
dashr.wait_for_text_to_equal(
109+
"#message-box2",
110+
"Multiple color values: #FF0000, #00FF00, #0000FF returned!"
111+
)

0 commit comments

Comments
 (0)