Compare commits

...

7 Commits

Author SHA1 Message Date
schloerke
f2be21861f devtools::document() (GitHub Actions) 2023-04-18 20:11:51 +00:00
Barret Schloerke
fce5216000 Add App State helper method to set options. Use it 2023-04-18 16:05:45 -04:00
Barret Schloerke
e418f3540a Use new getCurrentAppStateOptions() helper method paired with isRunning() 2023-04-18 16:05:45 -04:00
Barret Schloerke
977383de7f Move isRunning() to R/app-state.R to put all app state code in one location 2023-04-18 16:01:18 -04:00
Winston Chang
62bb21d5b6 Merge pull request #3804 from rstudio/docs/ex-download-button 2023-04-14 15:48:19 -05:00
Garrick Aden-Buie
4f85268d44 More complete downloadButton() example 2023-04-13 09:04:40 -04:00
Carson Sievert
611e517bb8 Rename actionQueue to taskQueue, add more context to the NEWS item (#3801) 2023-03-31 14:38:28 -05:00
16 changed files with 68 additions and 47 deletions

View File

@@ -6,7 +6,7 @@
### New features and improvements
* Closed #789: `<script>` loaded from dynamic UI are no longer loaded using synchronous `XMLHttpRequest` (via jQuery). (#3666)
* Closed #789: Dynamic UI is now rendered asynchronously, thanks in part to the newly exported `Shiny.renderDependenciesAsync()`, `Shiny.renderHtmlAsync()`, and `Shiny.renderContentAsync()`. Importantly, this means `<script>` tags are now loaded asynchronously (the old way used `XMLHttpRequest`, which is synchronous). In addition, `Shiny` now manages a queue of async tasks (exposed via `Shiny.shinyapp.taskQueue`) so that order of execution is preserved. (#3666)
* For `reactiveValues()` objects, whenever the `$names()` or `$values()` methods are called, the keys are now returned in the order that they were inserted. (#3774)

View File

@@ -7,6 +7,17 @@ NULL
.globals$appState <- NULL
#' Check whether a Shiny application is running
#'
#' This function tests whether a Shiny application is currently running.
#'
#' @return `TRUE` if a Shiny application is currently running. Otherwise,
#' `FALSE`.
#' @export
isRunning <- function() {
!is.null(getCurrentAppState())
}
initCurrentAppState <- function(appobj) {
if (!is.null(.globals$appState)) {
stop("Can't initialize current app state when another is currently active.")
@@ -21,6 +32,14 @@ getCurrentAppState <- function() {
.globals$appState
}
getCurrentAppStateOptions <- function() {
.globals$appState$options
}
setCurrentAppStateOptions <- function(options) {
stopifnot(isRunning())
.globals$appState$options <- options
}
clearCurrentAppState <- function() {
.globals$appState <- NULL
}

View File

@@ -1200,19 +1200,25 @@ uiOutput <- htmlOutput
#' @examples
#' \dontrun{
#' ui <- fluidPage(
#' p("Choose a dataset to download."),
#' selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
#' downloadButton("downloadData", "Download")
#' )
#'
#' server <- function(input, output) {
#' # Our dataset
#' data <- mtcars
#' # The requested dataset
#' data <- reactive({
#' get(input$dataset)
#' })
#'
#' output$downloadData <- downloadHandler(
#' filename = function() {
#' paste("data-", Sys.Date(), ".csv", sep="")
#' # Use the selected dataset as the suggested file name
#' paste0(input$dataset, ".csv")
#' },
#' content = function(file) {
#' write.csv(data, file)
#' # Write the dataset to the `file` that will be downloaded
#' write.csv(data(), file)
#' }
#' )
#' }

View File

@@ -274,7 +274,7 @@ MockShinySession <- R6Class(
self$token <- createUniqueId(16)
# Copy app-level options
self$options <- getCurrentAppState()$options
self$options <- getCurrentAppStateOptions()
self$cache <- cachem::cache_mem()
self$appcache <- cachem::cache_mem()

View File

@@ -495,16 +495,6 @@ serviceApp <- function() {
.shinyServerMinVersion <- '0.3.4'
#' Check whether a Shiny application is running
#'
#' This function tests whether a Shiny application is currently running.
#'
#' @return `TRUE` if a Shiny application is currently running. Otherwise,
#' `FALSE`.
#' @export
isRunning <- function() {
!is.null(getCurrentAppState())
}
# Returns TRUE if we're running in Shiny Server or other hosting environment,

View File

@@ -19,10 +19,10 @@ getShinyOption <- function(name, default = NULL) {
}
# Check if there's a current app
app_state <- getCurrentAppState()
if (!is.null(app_state)) {
if (name %in% names(app_state$options)) {
return(app_state$options[[name]])
if (isRunning()) {
app_state_options <- getCurrentAppStateOptions()
if (name %in% names(app_state_options)) {
return(app_state_options[[name]])
} else {
return(default)
}
@@ -199,11 +199,12 @@ shinyOptions <- function(...) {
# If not in a session, but we have a currently running app, modify options
# at the app level.
app_state <- getCurrentAppState()
if (!is.null(app_state)) {
if (isRunning()) {
# Modify app-level options
app_state$options <- dropNulls(mergeVectors(app_state$options, newOpts))
return(invisible(app_state$options))
setCurrentAppStateOptions(
dropNulls(mergeVectors(getCurrentAppStateOptions(), newOpts))
)
return(invisible(getCurrentAppStateOptions()))
}
# If no currently running app, modify global options and return them.
@@ -218,9 +219,8 @@ shinyOptions <- function(...) {
return(session$options)
}
app_state <- getCurrentAppState()
if (!is.null(app_state)) {
return(app_state$options)
if (isRunning()) {
return(getCurrentAppStateOptions())
}
return(.globals$options)

View File

@@ -738,7 +738,7 @@ ShinySession <- R6Class(
private$.outputOptions <- list()
# Copy app-level options
self$options <- getCurrentAppState()$options
self$options <- getCurrentAppStateOptions()
self$cache <- cachem::cache_mem(max_size = 200 * 1024^2)

View File

@@ -15583,7 +15583,7 @@
if (opts.priority === "event") {
this._sendNow();
} else if (!this.sendIsEnqueued) {
this.shinyapp.actionQueue.enqueue(function() {
this.shinyapp.taskQueue.enqueue(function() {
_this.sendIsEnqueued = false;
_this._sendNow();
});
@@ -17666,7 +17666,7 @@
function ShinyApp2() {
_classCallCheck36(this, ShinyApp2);
_defineProperty18(this, "$socket", null);
_defineProperty18(this, "actionQueue", new AsyncQueue());
_defineProperty18(this, "taskQueue", new AsyncQueue());
_defineProperty18(this, "config", null);
_defineProperty18(this, "$inputValues", {});
_defineProperty18(this, "$initialInput", null);
@@ -17875,7 +17875,7 @@
_this.startActionQueueLoop();
};
socket.onmessage = function(e) {
_this.actionQueue.enqueue(/* @__PURE__ */ _asyncToGenerator8(/* @__PURE__ */ _regeneratorRuntime8().mark(function _callee2() {
_this.taskQueue.enqueue(/* @__PURE__ */ _asyncToGenerator8(/* @__PURE__ */ _regeneratorRuntime8().mark(function _callee2() {
return _regeneratorRuntime8().wrap(function _callee2$(_context2) {
while (1)
switch (_context2.prev = _context2.next) {
@@ -17918,7 +17918,7 @@
break;
}
_context3.next = 3;
return this.actionQueue.dequeue();
return this.taskQueue.dequeue();
case 3:
action = _context3.sent;
_context3.prev = 4;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -36,19 +36,25 @@ function.
\examples{
\dontrun{
ui <- fluidPage(
p("Choose a dataset to download."),
selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
downloadButton("downloadData", "Download")
)
server <- function(input, output) {
# Our dataset
data <- mtcars
# The requested dataset
data <- reactive({
get(input$dataset)
})
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
# Use the selected dataset as the suggested file name
paste0(input$dataset, ".csv")
},
content = function(file) {
write.csv(data, file)
# Write the dataset to the `file` that will be downloaded
write.csv(data(), file)
}
)
}

View File

@@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
% Please edit documentation in R/app-state.R
\name{isRunning}
\alias{isRunning}
\title{Check whether a Shiny application is running}

View File

@@ -22,7 +22,7 @@ class InputBatchSender implements InputPolicy {
if (opts.priority === "event") {
this._sendNow();
} else if (!this.sendIsEnqueued) {
this.shinyapp.actionQueue.enqueue(() => {
this.shinyapp.taskQueue.enqueue(() => {
this.sendIsEnqueued = false;
this._sendNow();
});

View File

@@ -114,7 +114,7 @@ class ShinyApp {
// without overlapping. This is used for handling incoming messages from the
// server and scheduling outgoing messages to the server, and can be used for
// other things tasks as well.
actionQueue = new AsyncQueue<() => Promise<void> | void>();
taskQueue = new AsyncQueue<() => Promise<void> | void>();
config: {
workerId: string;
@@ -240,7 +240,7 @@ class ShinyApp {
this.startActionQueueLoop();
};
socket.onmessage = (e) => {
this.actionQueue.enqueue(async () => await this.dispatchMessage(e.data));
this.taskQueue.enqueue(async () => await this.dispatchMessage(e.data));
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
@@ -266,7 +266,7 @@ class ShinyApp {
async startActionQueueLoop(): Promise<void> {
// eslint-disable-next-line no-constant-condition
while (true) {
const action = await this.actionQueue.dequeue();
const action = await this.taskQueue.dequeue();
try {
await action();

View File

@@ -20,7 +20,7 @@ type MessageValue = Parameters<WebSocket["send"]>[0];
declare function addCustomMessageHandler(type: string, handler: Handler): void;
declare class ShinyApp {
$socket: ShinyWebSocket | null;
actionQueue: AsyncQueue<() => Promise<void> | void>;
taskQueue: AsyncQueue<() => Promise<void> | void>;
config: {
workerId: string;
sessionId: string;