Skip to content

Commit

Permalink
Provide support for no_update in Dash for R (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
rpkyle authored Aug 13, 2019
1 parent fcfedcb commit a44050a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 3 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

S3method(print,dash_component)
export(Dash)
export(dashNoUpdate)
export(input)
export(output)
export(state)
Expand Down
10 changes: 8 additions & 2 deletions R/dash.R
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,17 @@ Dash <- R6::R6Class(
output_value <- getStackTrace(do.call(callback, callback_args),
debug = private$debug,
pruned_errors = private$pruned_errors)

# reset callback context
private$callback_context_ <- NULL

if (is.null(private$stack_message)) {
# inspect the output_value to determine whether any outputs have no_update
# objects within them; these should not be updated
if (length(output_value) == 1 && class(output_value) == "no_update") {
response$body <- character(1) # return empty string
response$status <- 204L
}
else if (is.null(private$stack_message)) {
# pass on output_value to encode_plotly in case there are dccGraph
# components which include Plotly.js figures for which we'll need to
# run plotly_build from the plotly package
Expand Down
12 changes: 11 additions & 1 deletion R/dependencies.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
#' Use in conjunction with the `callback()` method from the [dash::Dash] class
#' to define the update logic in your application.
#'
#' The `dashNoUpdate()` function permits application developers to prevent a
#' single output from updating the layout. It has no formal arguments.
#'
#' @name dependencies
#' @param id a component id
#' @param property the component property to use


#' @rdname dependencies
#' @export
output <- function(id, property) {
Expand Down Expand Up @@ -44,3 +46,11 @@ dependency <- function(id = NULL, property = NULL) {
property = property
)
}

#' @rdname dependencies
#' @export
dashNoUpdate <- function() {
x <- list(NULL)
class(x) <- "no_update"
return(x)
}
7 changes: 7 additions & 0 deletions man/dependencies.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 111 additions & 0 deletions tests/integration/callbacks/test_no_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from selenium.webdriver.support.select import Select
import time

app = """
library(dash)
library(dashHtmlComponents)
library(dashCoreComponents)
app <- Dash$new()
app$layout(
htmlDiv(list(
dccDropdown(options = list(
list(label = "Red", value = "#FF0000"),
list(label = "Green", value = "#00FF00"),
list(label = "Blue", value = "#0000FF"),
list(label = "Do nothing", value = "nothing")
),
id = "color-selector"),
htmlButton(children = "Select all the colors!",
id = "multi-selector"
),
htmlDiv(id='message-box',
children='Please select a color choice from the dropdown menu.'),
htmlDiv(id='message-box2',
children=' ')
)
)
)
app$callback(output=list(id='message-box2', property='children'),
params=list(
input(id='multi-selector', property='n_clicks')),
function(n_clicks)
{
# if button has been clicked, n_clicks is numeric()
# on first launch of callback at layout initialization,
# value of n_clicks will be list(NULL), which is not
# comparable using >, < or =; hence the is.numeric()
# check
if (is.numeric(n_clicks) && n_clicks >= 1)
{
# return a vector to ensure that the check for
# class(x) == "no_update" isn't made for objects
# where length(x) > 1
return(c("Multiple color values: ",
"#FF0000, ",
"#00FF00, ",
"#0000FF ",
"returned!")
)
}
}
)
app$callback(output=list(id='message-box', property='children'),
params=list(
input(id='color-selector', property='value')),
function(color)
{
if (color %in% c("#FF0000", "#00FF00", "#0000FF")) {
msg <- sprintf("The hexadecimal representation of your last chosen color is %s",
color)
return(msg)
} else {
return(dashNoUpdate())
}
}
)
app$run_server()
"""


def test_rsnu001_no_update(dashr):
dashr.start_server(app)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[0].click()
dashr.wait_for_text_to_equal(
"#message-box",
"The hexadecimal representation of your last chosen color is #FF0000"
)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
time.sleep(1)
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #FF0000"
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[1].click()
dashr.wait_for_text_to_equal(
"#message-box",
"The hexadecimal representation of your last chosen color is #00FF00"
)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
time.sleep(1)
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #00FF00"
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[2].click()
dashr.wait_for_text_to_equal(
"#message-box",
"The hexadecimal representation of your last chosen color is #0000FF"
)
dashr.find_element("#color-selector").click()
dashr.find_elements("div.VirtualizedSelectOption")[3].click()
time.sleep(1)
assert dashr.find_element("#message-box").text == "The hexadecimal representation of your last chosen color is #0000FF"
dashr.find_element("#multi-selector").click()
dashr.wait_for_text_to_equal(
"#message-box2",
"Multiple color values: #FF0000, #00FF00, #0000FF returned!"
)

0 comments on commit a44050a

Please sign in to comment.