Skip to content

Commit

Permalink
refactor(framework): Deprecate RenderScheduler in favor of Render.js (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
vladitasev committed Feb 1, 2021
1 parent d8ea023 commit bc78857
Show file tree
Hide file tree
Showing 32 changed files with 280 additions and 247 deletions.
4 changes: 2 additions & 2 deletions packages/base/bundle.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import "./dist/test-resources/elements/GenericExt.js";
import "./dist/test-resources/assets/Themes.js";

// used in test pages
import RenderScheduler from "./dist/RenderScheduler.js";
window.RenderScheduler = RenderScheduler;
import { renderFinished } from "./dist/Render.js";
import { isIE } from "./dist/Device.js";
window.isIE = isIE; // attached to the window object for testing purposes

Expand Down Expand Up @@ -52,4 +51,5 @@ window["sap-ui-webcomponents-bundle"] = {
registerI18nBundle,
fetchI18nBundle,
getI18nBundle,
renderFinished,
};
2 changes: 1 addition & 1 deletion packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"devDependencies": {
"@ui5/webcomponents-tools": "1.0.0-rc.11",
"array-uniq": "^2.0.0",
"chromedriver": "87.0.5",
"chromedriver": "88.0.0",
"copy-and-watch": "^0.1.4",
"eslint": "^5.13.0",
"eslint-config-airbnb-base": "^13.1.0",
Expand Down
174 changes: 174 additions & 0 deletions packages/base/src/Render.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import EventProvider from "./EventProvider.js";
import RenderQueue from "./RenderQueue.js";
import { getAllRegisteredTags } from "./CustomElementsRegistry.js";
import { isRtlAware } from "./locale/RTLAwareRegistry.js";

const registeredElements = new Set();
const eventProvider = new EventProvider();

const invalidatedWebComponents = new RenderQueue(); // Queue for invalidated web components

let renderTaskPromise,
renderTaskPromiseResolve;

let mutationObserverTimer;

let queuePromise;


/**
* Schedules a render task (if not already scheduled) to render the component
*
* @param webComponent
* @returns {Promise}
*/
const renderDeferred = async webComponent => {
// Enqueue the web component
invalidatedWebComponents.add(webComponent);

// Schedule a rendering task
await scheduleRenderTask();
};

/**
* Renders a component synchronously and adds it to the registry of rendered components
*
* @param webComponent
*/
const renderImmediately = webComponent => {
eventProvider.fireEvent("beforeComponentRender", webComponent);
registeredElements.add(webComponent);
webComponent._render();
};

/**
* Cancels the rendering of a component, if awaiting to be rendered, and removes it from the registry of rendered components
*
* @param webComponent
*/
const cancelRender = webComponent => {
invalidatedWebComponents.remove(webComponent);
registeredElements.delete(webComponent);
};

/**
* Schedules a rendering task, if not scheduled already
*/
const scheduleRenderTask = async () => {
if (!queuePromise) {
queuePromise = new Promise(resolve => {
window.requestAnimationFrame(() => {
// Render all components in the queue

// console.log(`--------------------RENDER TASK START------------------------------`); // eslint-disable-line
invalidatedWebComponents.process(renderImmediately);
// console.log(`--------------------RENDER TASK END------------------------------`); // eslint-disable-line

// Resolve the promise so that callers of renderDeferred can continue
queuePromise = null;
resolve();

// Wait for Mutation observer before the render task is considered finished
if (!mutationObserverTimer) {
mutationObserverTimer = setTimeout(() => {
mutationObserverTimer = undefined;
if (invalidatedWebComponents.isEmpty()) {
_resolveTaskPromise();
}
}, 200);
}
});
});
}

await queuePromise;
};

/**
* return a promise that will be resolved once all invalidated web components are rendered
*/
const whenDOMUpdated = () => {
if (renderTaskPromise) {
return renderTaskPromise;
}

renderTaskPromise = new Promise(resolve => {
renderTaskPromiseResolve = resolve;
window.requestAnimationFrame(() => {
if (invalidatedWebComponents.isEmpty()) {
renderTaskPromise = undefined;
resolve();
}
});
});

return renderTaskPromise;
};

const whenAllCustomElementsAreDefined = () => {
const definedPromises = getAllRegisteredTags().map(tag => customElements.whenDefined(tag));
return Promise.all(definedPromises);
};

const renderFinished = async () => {
await whenAllCustomElementsAreDefined();
await whenDOMUpdated();
};

const _resolveTaskPromise = () => {
if (!invalidatedWebComponents.isEmpty()) {
// More updates are pending. Resolve will be called again
return;
}

if (renderTaskPromiseResolve) {
renderTaskPromiseResolve();
renderTaskPromiseResolve = undefined;
renderTaskPromise = undefined;
}
};

/**
* Re-renders all UI5 Elements on the page, with the option to specify filters to rerender only some components.
*
* Usage:
* reRenderAllUI5Elements() -> re-renders all components
* reRenderAllUI5Elements({tag: "ui5-button"}) -> re-renders only instances of ui5-button
* reRenderAllUI5Elements({rtlAware: true}) -> re-renders only rtlAware components
* reRenderAllUI5Elements({languageAware: true}) -> re-renders only languageAware components
* reRenderAllUI5Elements({rtlAware: true, languageAware: true}) -> re-renders components that are rtlAware or languageAware
* etc...
*
* @public
* @param {Object|undefined} filters - Object with keys that can be "rtlAware" or "languageAware"
* @returns {Promise<void>}
*/
const reRenderAllUI5Elements = async filters => {
registeredElements.forEach(element => {
const tag = element.constructor.getMetadata().getTag();
const rtlAware = isRtlAware(element.constructor);
const languageAware = element.constructor.getMetadata().isLanguageAware();
if (!filters || (filters.tag === tag) || (filters.rtlAware && rtlAware) || (filters.languageAware && languageAware)) {
renderDeferred(element);
}
});
await renderFinished();
};

const attachBeforeComponentRender = listener => {
eventProvider.attachEvent("beforeComponentRender", listener);
};

const detachBeforeComponentRender = listener => {
eventProvider.detachEvent("beforeComponentRender", listener);
};

export {
renderDeferred,
renderImmediately,
cancelRender,
renderFinished,
reRenderAllUI5Elements,
attachBeforeComponentRender,
detachBeforeComponentRender,
};
168 changes: 16 additions & 152 deletions packages/base/src/RenderScheduler.js
Original file line number Diff line number Diff line change
@@ -1,179 +1,43 @@
import EventProvider from "./EventProvider.js";
import RenderQueue from "./RenderQueue.js";
import { getAllRegisteredTags } from "./CustomElementsRegistry.js";
import { isRtlAware } from "./locale/RTLAwareRegistry.js";

const registeredElements = new Set();
const eventProvider = new EventProvider();

// Queue for invalidated web components
const invalidatedWebComponents = new RenderQueue();

let renderTaskPromise,
renderTaskPromiseResolve;

let mutationObserverTimer;

let queuePromise;
import {
renderDeferred,
renderImmediately,
renderFinished,
} from "./Render.js";

/**
* Class that manages the rendering/re-rendering of web components
* This is always asynchronous
* @deprecated Use the Render.js module instead
*/
class RenderScheduler {
constructor() {
throw new Error("Static class");
}

/**
* Schedules a render task (if not already scheduled) to render the component
*
* @param webComponent
* @returns {Promise}
* @deprecated Use renderDeferred from the Render.js module instead
*/
static async renderDeferred(webComponent) {
// Enqueue the web component
invalidatedWebComponents.add(webComponent);

// Schedule a rendering task
await RenderScheduler.scheduleRenderTask();
console.log("RenderScheduler.renderDeferred is deprecated, please use renderDeferred, exported by Render.js instead"); // eslint-disable-line
await renderDeferred(webComponent);
}

/**
* Renders a component synchronously
*
* @param webComponent
* @deprecated Use renderImmediately from the Render.js module instead
*/
static renderImmediately(webComponent) {
eventProvider.fireEvent("beforeComponentRender", webComponent);
webComponent._render();
console.log("RenderScheduler.renderImmediately is deprecated, please use renderImmediately, exported by Render.js instead"); // eslint-disable-line
return renderImmediately(webComponent);
}

/**
* Cancels the rendering of a component, added to the queue with renderDeferred
*
* @param webComponent
* @deprecated Use renderFinished from the Render.js module instead
* @returns {Promise<void>}
*/
static cancelRender(webComponent) {
invalidatedWebComponents.remove(webComponent);
}

/**
* Schedules a rendering task, if not scheduled already
*/
static async scheduleRenderTask() {
if (!queuePromise) {
queuePromise = new Promise(resolve => {
window.requestAnimationFrame(() => {
// Render all components in the queue

// console.log(`--------------------RENDER TASK START------------------------------`); // eslint-disable-line
invalidatedWebComponents.process(RenderScheduler.renderImmediately);
// console.log(`--------------------RENDER TASK END------------------------------`); // eslint-disable-line

// Resolve the promise so that callers of renderDeferred can continue
queuePromise = null;
resolve();

// Wait for Mutation observer before the render task is considered finished
if (!mutationObserverTimer) {
mutationObserverTimer = setTimeout(() => {
mutationObserverTimer = undefined;
if (invalidatedWebComponents.isEmpty()) {
RenderScheduler._resolveTaskPromise();
}
}, 200);
}
});
});
}

await queuePromise;
}

/**
* return a promise that will be resolved once all invalidated web components are rendered
*/
static whenDOMUpdated() {
if (renderTaskPromise) {
return renderTaskPromise;
}

renderTaskPromise = new Promise(resolve => {
renderTaskPromiseResolve = resolve;
window.requestAnimationFrame(() => {
if (invalidatedWebComponents.isEmpty()) {
renderTaskPromise = undefined;
resolve();
}
});
});

return renderTaskPromise;
}

static whenAllCustomElementsAreDefined() {
const definedPromises = getAllRegisteredTags().map(tag => customElements.whenDefined(tag));
return Promise.all(definedPromises);
}

static async whenFinished() {
await RenderScheduler.whenAllCustomElementsAreDefined();
await RenderScheduler.whenDOMUpdated();
}

static _resolveTaskPromise() {
if (!invalidatedWebComponents.isEmpty()) {
// More updates are pending. Resolve will be called again
return;
}

if (renderTaskPromiseResolve) {
renderTaskPromiseResolve.call(this);
renderTaskPromiseResolve = undefined;
renderTaskPromise = undefined;
}
}

static register(element) {
registeredElements.add(element);
}

static deregister(element) {
registeredElements.delete(element);
}

/**
* Re-renders all UI5 Elements on the page, with the option to specify filters to rerender only some components.
*
* Usage:
* reRenderAllUI5Elements() -> rerenders all components
* reRenderAllUI5Elements({tag: "ui5-button"}) -> re-renders only instances of ui5-button
* reRenderAllUI5Elements({rtlAware: true}) -> re-renders only rtlAware components
* reRenderAllUI5Elements({languageAware: true}) -> re-renders only languageAware components
* reRenderAllUI5Elements({rtlAware: true, languageAware: true}) -> re-renders components that are rtlAware or languageAware
* etc...
*
* @public
* @param {Object|undefined} filters - Object with keys that can be "rtlAware" or "languageAware"
*/
static reRenderAllUI5Elements(filters) {
registeredElements.forEach(element => {
const tag = element.constructor.getMetadata().getTag();
const rtlAware = isRtlAware(element.constructor);
const languageAware = element.constructor.getMetadata().isLanguageAware();
if (!filters || (filters.tag === tag) || (filters.rtlAware && rtlAware) || (filters.languageAware && languageAware)) {
RenderScheduler.renderDeferred(element);
}
});
}

static attachBeforeComponentRender(listener) {
eventProvider.attachEvent("beforeComponentRender", listener);
}

static detachBeforeComponentRender(listener) {
eventProvider.detachEvent("beforeComponentRender", listener);
console.log("RenderScheduler.whenFinished is deprecated, please use renderFinished, exported by Render.js instead"); // eslint-disable-line
await renderFinished();
}
}

Expand Down
Loading

0 comments on commit bc78857

Please sign in to comment.