mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 16:08:19 -05:00
Compare commits
6 Commits
bindAfterR
...
3806-no-ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8a91fb2e3 | ||
|
|
7909389f0a | ||
|
|
596165e473 | ||
|
|
62bb21d5b6 | ||
|
|
4f85268d44 | ||
|
|
611e517bb8 |
2
NEWS.md
2
NEWS.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
|
||||
29
R/shinyui.R
29
R/shinyui.R
@@ -231,11 +231,32 @@ uiHttpHandler <- function(ui, uiPattern = "^/$") {
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
if (inherits(uiValue, "httpResponse")) {
|
||||
return(uiValue)
|
||||
} else {
|
||||
# Avoid caching the UI response to ensure that UI is always re-evaluated.
|
||||
# This is necessary for getCurrentTheme() to be known, required for BS4+.
|
||||
no_cache_headers <- list(
|
||||
"Cache-Control" = "no-cache, no-store, must-revalidate",
|
||||
"Pragma" = "no-cache",
|
||||
"Expires" = "0"
|
||||
)
|
||||
|
||||
if (!inherits(uiValue, "httpResponse")) {
|
||||
html <- renderPage(uiValue, showcaseMode, testMode)
|
||||
return(httpResponse(200, content=html))
|
||||
uiValue <- httpResponse(200, content=html)
|
||||
}
|
||||
|
||||
# 2023-04-23 jcheng5: Example app in PR comment
|
||||
# https://github.com/rstudio/shiny/pull/3810#issuecomment-1513828996
|
||||
# > I think we should always add the cache-busting headers. Without it,
|
||||
# Connect and ShinyApps.io configurations will have problems in that the
|
||||
# workerId could be a stale value.
|
||||
if (
|
||||
# The user has not set a `Cache-Control` policy within their UI func
|
||||
!"Cache-Control" %in% names(uiValue$headers)
|
||||
) {
|
||||
# Disable caching for the UI's response
|
||||
uiValue$headers <- utils::modifyList(uiValue$headers, no_cache_headers)
|
||||
}
|
||||
|
||||
uiValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
2
inst/www/shared/shiny.min.js
vendored
2
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
2
srcts/types/src/shiny/shinyapp.d.ts
vendored
2
srcts/types/src/shiny/shinyapp.d.ts
vendored
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user