mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 15:38:19 -05:00
Compare commits
6 Commits
v1.12.0
...
async-load
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e934797c9 | ||
|
|
581ace76e4 | ||
|
|
e43609b60a | ||
|
|
d0bf86e5e2 | ||
|
|
b023350b90 | ||
|
|
7bccfeb774 |
File diff suppressed because it is too large
Load Diff
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
@@ -1,4 +1,5 @@
|
||||
import { mergeSort } from "../utils";
|
||||
import { Callbacks } from "../utils/callbacks";
|
||||
|
||||
interface BindingBase {
|
||||
name: string;
|
||||
@@ -14,6 +15,7 @@ class BindingRegistry<Binding extends BindingBase> {
|
||||
name: string;
|
||||
bindings: Array<BindingObj<Binding>> = [];
|
||||
bindingNames: { [key: string]: BindingObj<Binding> } = {};
|
||||
registerCallbacks: Callbacks = new Callbacks();
|
||||
|
||||
register(binding: Binding, bindingName: string, priority = 0): void {
|
||||
const bindingObj = { binding, priority };
|
||||
@@ -23,6 +25,12 @@ class BindingRegistry<Binding extends BindingBase> {
|
||||
this.bindingNames[bindingName] = bindingObj;
|
||||
binding.name = bindingName;
|
||||
}
|
||||
|
||||
this.registerCallbacks.invoke();
|
||||
}
|
||||
|
||||
onRegister(fn: () => void, once = true): void {
|
||||
this.registerCallbacks.register(fn, once);
|
||||
}
|
||||
|
||||
setPriority(bindingName: string, priority: number): void {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
import { bindAll, unbindAll, _bindAll } from "./bind";
|
||||
import type { BindInputsCtx, BindScope } from "./bind";
|
||||
import { setShinyObj } from "./initedMethods";
|
||||
import { registerDependency } from "./render";
|
||||
import { registerDependency, renderHtml } from "./render";
|
||||
import { sendImageSizeFns } from "./sendImageSize";
|
||||
import { ShinyApp } from "./shinyapp";
|
||||
import { registerNames as singletonsRegisterNames } from "./singletons";
|
||||
@@ -150,6 +150,19 @@ function initShiny(windowShiny: Shiny): void {
|
||||
(x) => x.value
|
||||
);
|
||||
|
||||
// When future bindings are registered via dynamic UI, check to see if renderHtml()
|
||||
// is currently executing. If it's not, it's likely that the binding registration
|
||||
// is occurring a tick after renderHtml()/renderContent(), in which case we need
|
||||
// to make sure the new bindings get a chance to bind to the DOM. (#3635)
|
||||
const maybeBindOnRegister = debounce(0, () => {
|
||||
if (!renderHtml.isExecuting()) {
|
||||
windowShiny.bindAll(document.documentElement);
|
||||
}
|
||||
});
|
||||
|
||||
inputBindings.onRegister(maybeBindOnRegister, false);
|
||||
outputBindings.onRegister(maybeBindOnRegister, false);
|
||||
|
||||
// The server needs to know the size of each image and plot output element,
|
||||
// in case it is auto-sizing
|
||||
$(".shiny-image-output, .shiny-plot-output, .shiny-report-size").each(
|
||||
|
||||
@@ -72,10 +72,20 @@ function renderHtml(
|
||||
dependencies: HtmlDep[],
|
||||
where: WherePosition = "replace"
|
||||
): ReturnType<typeof singletonsRenderHtml> {
|
||||
renderDependencies(dependencies);
|
||||
return singletonsRenderHtml(html, el, where);
|
||||
renderHtml._renderCount++;
|
||||
try {
|
||||
renderDependencies(dependencies);
|
||||
return singletonsRenderHtml(html, el, where);
|
||||
} finally {
|
||||
renderHtml._renderCount--;
|
||||
}
|
||||
}
|
||||
|
||||
renderHtml._renderCount = 0;
|
||||
renderHtml.isExecuting = function () {
|
||||
return renderHtml._renderCount > 0;
|
||||
};
|
||||
|
||||
type HtmlDepVersion = string;
|
||||
|
||||
type MetaItem = {
|
||||
@@ -199,6 +209,9 @@ function renderDependency(dep_: HtmlDep) {
|
||||
$head.append(stylesheetLinks);
|
||||
}
|
||||
|
||||
const scriptPromises: Array<Promise<any>> = [];
|
||||
const scriptElements: HTMLScriptElement[] = [];
|
||||
|
||||
dep.script.forEach((x) => {
|
||||
const script = document.createElement("script");
|
||||
|
||||
@@ -210,9 +223,23 @@ function renderDependency(dep_: HtmlDep) {
|
||||
script.setAttribute(attr, val ? val : "");
|
||||
});
|
||||
|
||||
$head.append(script);
|
||||
const p = new Promise((resolve) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
script.onload = (e: Event) => {
|
||||
resolve(null);
|
||||
};
|
||||
});
|
||||
|
||||
scriptPromises.push(p);
|
||||
scriptElements.push(script);
|
||||
});
|
||||
|
||||
// Append the script elements all at once, so that we're sure they'll load in
|
||||
// order. (We didn't append them individually in the `forEach()` above,
|
||||
// because we're not sure that the browser will load them in order if done
|
||||
// that way.)
|
||||
document.head.append(...scriptElements);
|
||||
|
||||
dep.attachment.forEach((x) => {
|
||||
const link = $("<link rel='attachment'>")
|
||||
.attr("id", dep.name + "-" + x.key + "-attachment")
|
||||
@@ -221,12 +248,22 @@ function renderDependency(dep_: HtmlDep) {
|
||||
$head.append(link);
|
||||
});
|
||||
|
||||
if (dep.head) {
|
||||
const $newHead = $("<head></head>");
|
||||
Promise.allSettled(scriptPromises).then(() => {
|
||||
// After the scripts are all loaded, insert any head content. This may
|
||||
// contain <script> tags with inline content, which we want to execute after
|
||||
// the script elements above, because the code here may depend on them.
|
||||
if (dep.head) {
|
||||
const $newHead = $("<head></head>");
|
||||
|
||||
$newHead.html(dep.head);
|
||||
$head.append($newHead.children());
|
||||
}
|
||||
|
||||
// Bind all
|
||||
shinyInitializeInputs(document.body);
|
||||
shinyBindAll(document.body);
|
||||
});
|
||||
|
||||
$newHead.html(dep.head);
|
||||
$head.append($newHead.children());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
45
srcts/src/utils/callbacks.ts
Normal file
45
srcts/src/utils/callbacks.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
type Cb = {
|
||||
once: boolean;
|
||||
fn: () => void;
|
||||
};
|
||||
|
||||
type Cbs = {
|
||||
[key: string]: Cb;
|
||||
};
|
||||
|
||||
class Callbacks {
|
||||
callbacks: Cbs = {};
|
||||
id = 0;
|
||||
|
||||
register(fn: () => void, once = true): () => void {
|
||||
this.id += 1;
|
||||
const id = this.id;
|
||||
|
||||
this.callbacks[id] = { fn, once };
|
||||
return () => {
|
||||
delete this.callbacks[id];
|
||||
};
|
||||
}
|
||||
|
||||
invoke(): void {
|
||||
for (const id in this.callbacks) {
|
||||
const cb = this.callbacks[id];
|
||||
|
||||
try {
|
||||
cb.fn();
|
||||
} finally {
|
||||
if (cb.once) delete this.callbacks[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.callbacks = {};
|
||||
}
|
||||
|
||||
count(): number {
|
||||
return Object.keys(this.callbacks).length;
|
||||
}
|
||||
}
|
||||
|
||||
export { Callbacks };
|
||||
3
srcts/types/src/bindings/registry.d.ts
vendored
3
srcts/types/src/bindings/registry.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { Callbacks } from "../utils/callbacks";
|
||||
interface BindingBase {
|
||||
name: string;
|
||||
}
|
||||
@@ -12,7 +13,9 @@ declare class BindingRegistry<Binding extends BindingBase> {
|
||||
bindingNames: {
|
||||
[key: string]: BindingObj<Binding>;
|
||||
};
|
||||
registerCallbacks: Callbacks;
|
||||
register(binding: Binding, bindingName: string, priority?: number): void;
|
||||
onRegister(fn: () => void, once?: boolean): void;
|
||||
setPriority(bindingName: string, priority: number): void;
|
||||
getPriority(bindingName: string): number | false;
|
||||
getBindings(): Array<BindingObj<Binding>>;
|
||||
|
||||
4
srcts/types/src/shiny/render.d.ts
vendored
4
srcts/types/src/shiny/render.d.ts
vendored
@@ -7,6 +7,10 @@ declare function renderContent(el: BindScope, content: string | {
|
||||
deps?: HtmlDep[];
|
||||
} | null, where?: WherePosition): void;
|
||||
declare function renderHtml(html: string, el: BindScope, dependencies: HtmlDep[], where?: WherePosition): ReturnType<typeof singletonsRenderHtml>;
|
||||
declare namespace renderHtml {
|
||||
var _renderCount: number;
|
||||
var isExecuting: () => boolean;
|
||||
}
|
||||
declare type HtmlDepVersion = string;
|
||||
declare type MetaItem = {
|
||||
name: string;
|
||||
|
||||
16
srcts/types/src/utils/callbacks.d.ts
vendored
Normal file
16
srcts/types/src/utils/callbacks.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
declare type Cb = {
|
||||
once: boolean;
|
||||
fn: () => void;
|
||||
};
|
||||
declare type Cbs = {
|
||||
[key: string]: Cb;
|
||||
};
|
||||
declare class Callbacks {
|
||||
callbacks: Cbs;
|
||||
id: number;
|
||||
register(fn: () => void, once?: boolean): () => void;
|
||||
invoke(): void;
|
||||
clear(): void;
|
||||
count(): number;
|
||||
}
|
||||
export { Callbacks };
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"declaration": true,
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"target": "es2020",
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "./srcts/types",
|
||||
"emitDeclarationOnly": true,
|
||||
"moduleResolution": "node",
|
||||
// Can not use `types: []` to disable injecting NodeJS types. More types are
|
||||
// needed than just the DOM's `window.setTimeout`
|
||||
// "types": [],
|
||||
|
||||
Reference in New Issue
Block a user