Skip to content

Commit a44050a

Browse files
authored
Provide support for no_update in Dash for R (#111)
1 parent fcfedcb commit a44050a

File tree

5 files changed

+138
-3
lines changed

5 files changed

+138
-3
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,17 @@ Dash <- R6::R6Class(
287287
output_value <- getStackTrace(do.call(callback, callback_args),
288288
debug = private$debug,
289289
pruned_errors = private$pruned_errors)
290-
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

R/dependencies.R

Lines changed: 11 additions & 1 deletion
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+
}

man/dependencies.Rd

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 111 additions & 0 deletions
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)