@eclipse-scout/core
Version:
Eclipse Scout runtime
159 lines (140 loc) • 4.73 kB
text/typescript
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {App, arrays, Device, HtmlComponent} from '../index';
export class LayoutValidator {
/** @internal */
_invalidComponents: HtmlComponent[];
protected _validateTimeoutId: number | boolean;
protected _postValidateFunctions: (() => void)[];
protected _suppressValidate: boolean;
constructor() {
this._invalidComponents = [];
this._validateTimeoutId = null;
this._postValidateFunctions = [];
this._suppressValidate = false;
}
invalidateTree(htmlComp: HtmlComponent) {
let validateRoot,
htmlParent = htmlComp,
htmlSource = htmlComp;
// Mark every parent as invalid until validate root
while (htmlParent) {
htmlComp = htmlParent;
htmlComp.invalidateLayout(htmlSource);
if (htmlComp.isValidateRoot()) {
validateRoot = htmlComp;
break;
}
htmlParent = htmlComp.getParent();
}
if (!htmlParent) {
validateRoot = htmlComp;
}
this.invalidate(validateRoot);
}
invalidate(htmlComp: HtmlComponent) {
let position = 0;
// Don't insert if already inserted...
// Info: when component is already in list but no one triggers validation,
// validation is never scheduled that's why we call scheduleValidation here.
if (this._invalidComponents.indexOf(htmlComp) >= 0) {
this._scheduleValidation(); // ... but schedule validation
return;
}
// Make sure it will be inserted before any descendant
// This prevents multiple layouting of the descendant
this._invalidComponents.forEach((invalidComponent, i) => {
if (invalidComponent.isDescendantOf(htmlComp)) {
return;
}
position++;
}, this);
// Add validate root to list of invalid components. These are the starting point for a subsequent call to validate().
arrays.insert(this._invalidComponents, htmlComp, position);
this._scheduleValidation();
}
protected _scheduleValidation() {
if (this._validateTimeoutId !== null) {
// Task already scheduled
return;
}
if (Device.get().supportsMicrotask()) {
this._validateTimeoutId = true; // boolean is sufficient, an id is only necessary for legacy purpose when setTimeout is used
queueMicrotask(() => {
// Validate, but only if still required
if (this._validateTimeoutId) {
try {
this.validate();
} catch (e) {
// Without handling the exception it would be swallowed (as it happens for promises)
App.get().errorHandler.handle(e);
}
}
});
} else {
this._validateTimeoutId = setTimeout(() => this.validate());
}
}
/**
* Layouts all invalid components (unless they have been removed).
*/
validate() {
if (!Device.get().supportsMicrotask()) {
clearTimeout(this._validateTimeoutId as number);
}
this._validateTimeoutId = null;
if (this._suppressValidate) {
return;
}
this._invalidComponents.slice().forEach(comp => {
if (comp.validateLayout()) {
arrays.remove(this._invalidComponents, comp);
}
});
this._postValidateFunctions.splice(0).forEach(func => func());
}
/**
* Suppresses every upcoming validations. The caller has to call {@link unsuppressValidate} to reactive validation again.
* Can be useful if the browser starts the processing of the micro task queue unexpectedly (e.g. when inserting certain DOM elements).
*/
suppressValidate() {
this._suppressValidate = true;
}
unsuppressValidate() {
this._suppressValidate = false;
this._scheduleValidation();
}
/**
* Removes those components from this._invalidComponents which have the given container as ancestor.
* The idea is to remove all components whose ancestor is about to be removed from the DOM.
*/
cleanupInvalidComponents($parentContainer: JQuery) {
this._invalidComponents.slice().forEach(comp => {
if (comp.$comp.closest($parentContainer).length > 0) {
arrays.remove(this._invalidComponents, comp);
}
});
}
/**
* Runs the given function at the end of {@link validate}.
*/
schedulePostValidateFunction(func: () => void) {
if (!func) {
return;
}
this._postValidateFunctions.push(func);
}
removePostValidateFunction(func: () => void) {
if (!func) {
return;
}
arrays.remove(this._postValidateFunctions, func);
}
}