/**
* Manages form field dependencies.
*
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager
- * @since 5.2
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager
+ * @since 5.2
*/
-define(['Dictionary', 'Dom/ChangeListener', 'EventHandler', 'List', 'Dom/Util', 'ObjectMap'], function (Dictionary, DomChangeListener, EventHandler, List, DomUtil, ObjectMap) {
+define(["require", "exports", "tslib", "../../../../Dom/Util", "../../../../Event/Handler", "../../../../Core"], function (require, exports, tslib_1, Util_1, EventHandler, Core) {
"use strict";
- /**
- * is `true` if containters are currently checked for their availablility, otherwise `false`
- * @type {boolean}
- * @private
- */
- var _checkingContainers = false;
- /**
- * is `true` if containter will be checked again after the current check for their availablility
- * has finished, otherwise `false`
- * @type {boolean}
- * @private
- */
- var _checkContainersAgain = true;
- /**
- * list of containers hidden due to their own dependencies
- * @type {List}
- * @private
- */
- var _dependencyHiddenNodes = new List();
- /**
- * list of fields for which event listeners have been registered
- * @type {Dictionary}
- * @private
- */
- var _fields = new Dictionary();
- /**
- * list of registered forms
- * @type {List}
- * @private
- */
- var _forms = new List();
- /**
- * list of dependencies grouped by the dependent node they belong to
- * @type {Dictionary}
- * @private
- */
- var _nodeDependencies = new Dictionary();
- /**
- * cache of validation-related properties of hidden form fields
- * @type {ObjectMap}
- * @private
- */
- var _validatedFieldProperties = new ObjectMap();
- return {
+ Util_1 = tslib_1.__importDefault(Util_1);
+ EventHandler = tslib_1.__importStar(EventHandler);
+ Core = tslib_1.__importStar(Core);
+ const _dependencyHiddenNodes = new Set();
+ const _fields = new Map();
+ const _forms = new Set();
+ const _nodeDependencies = new Map();
+ const _validatedFieldProperties = new WeakMap();
+ let _checkingContainers = false;
+ let _checkContainersAgain = true;
+ const Manager = {
/**
* Hides the given node because of its own dependencies.
- *
- * @param {HTMLElement} node hidden node
- * @protected
*/
- _hide: function (node) {
- elHide(node);
+ _hide(node) {
+ node.style.display = "none";
_dependencyHiddenNodes.add(node);
// also hide tab menu entry
- if (node.classList.contains('tabMenuContent')) {
- elBySelAll('li', node.parentNode.querySelector('.tabMenu'), function (tabLink) {
- if (elData(tabLink, 'name') === elData(node, 'name')) {
- elHide(tabLink);
+ if (node.classList.contains("tabMenuContent")) {
+ node
+ .parentNode.querySelector(".tabMenu")
+ .querySelectorAll("li")
+ .forEach((tabLink) => {
+ if (tabLink.dataset.name === node.dataset.name) {
+ tabLink.style.display = "none";
}
});
}
- elBySelAll('[max], [maxlength], [min], [required]', node, function (validatedField) {
- var properties = new Dictionary();
- var max = elAttr(validatedField, 'max');
+ node.querySelectorAll("[max], [maxlength], [min], [required]").forEach((validatedField) => {
+ const properties = new Map();
+ const max = validatedField.getAttribute("max");
if (max) {
- properties.set('max', max);
- validatedField.removeAttribute('max');
+ properties.set("max", max);
+ validatedField.removeAttribute("max");
}
- var maxlength = elAttr(validatedField, 'maxlength');
+ const maxlength = validatedField.getAttribute("maxlength");
if (maxlength) {
- properties.set('maxlength', maxlength);
- validatedField.removeAttribute('maxlength');
+ properties.set("maxlength", maxlength);
+ validatedField.removeAttribute("maxlength");
}
- var min = elAttr(validatedField, 'min');
+ const min = validatedField.getAttribute("min");
if (min) {
- properties.set('min', min);
- validatedField.removeAttribute('min');
+ properties.set("min", min);
+ validatedField.removeAttribute("min");
}
if (validatedField.required) {
- properties.set('required', true);
- validatedField.removeAttribute('required');
+ properties.set("required", "true");
+ validatedField.removeAttribute("required");
}
_validatedFieldProperties.set(validatedField, properties);
});
},
/**
* Shows the given node because of its own dependencies.
- *
- * @param {HTMLElement} node shown node
- * @protected
*/
- _show: function (node) {
- elShow(node);
+ _show(node) {
+ node.style.display = "block";
_dependencyHiddenNodes.delete(node);
// also show tab menu entry
- if (node.classList.contains('tabMenuContent')) {
- elBySelAll('li', node.parentNode.querySelector('.tabMenu'), function (tabLink) {
- if (elData(tabLink, 'name') === elData(node, 'name')) {
- elShow(tabLink);
+ if (node.classList.contains("tabMenuContent")) {
+ node
+ .parentNode.querySelector(".tabMenu")
+ .querySelectorAll("li")
+ .forEach((tabLink) => {
+ if (tabLink.dataset.name === node.dataset.name) {
+ tabLink.style.display = "block";
}
});
}
- elBySelAll('input, select', node, function (validatedField) {
+ node.querySelectorAll("input, select").forEach((validatedField) => {
// if a container is shown, ignore all fields that
// have a hidden parent element within the container
- var parentNode = validatedField.parentNode;
- while (parentNode !== node && parentNode.style.getPropertyValue('display') !== 'none') {
+ let parentNode = validatedField.parentNode;
+ while (parentNode !== node && parentNode.style.getPropertyValue("display") !== "none") {
parentNode = parentNode.parentNode;
}
if (parentNode === node && _validatedFieldProperties.has(validatedField)) {
- var properties = _validatedFieldProperties.get(validatedField);
- if (properties.has('max')) {
- elAttr(validatedField, 'max', properties.get('max'));
+ const properties = _validatedFieldProperties.get(validatedField);
+ if (properties.has("max")) {
+ validatedField.setAttribute("max", properties.get("max"));
}
- if (properties.has('maxlength')) {
- elAttr(validatedField, 'maxlength', properties.get('maxlength'));
+ if (properties.has("maxlength")) {
+ validatedField.setAttribute("maxlength", properties.get("maxlength"));
}
- if (properties.has('min')) {
- elAttr(validatedField, 'min', properties.get('min'));
+ if (properties.has("min")) {
+ validatedField.setAttribute("min", properties.get("min"));
}
- if (properties.has('required')) {
- elAttr(validatedField, 'required', '');
+ if (properties.has("required")) {
+ validatedField.setAttribute("required", "");
}
_validatedFieldProperties.delete(validatedField);
}
});
},
+ /**
+ * Adds the given callback to the list of callbacks called when checking containers.
+ */
+ addContainerCheckCallback(callback) {
+ if (typeof callback !== "function") {
+ throw new TypeError("Expected a valid callback for parameter 'callback'.");
+ }
+ EventHandler.add("com.woltlab.wcf.form.builder.dependency", "checkContainers", callback);
+ },
/**
* Registers a new form field dependency.
- *
- * @param {WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract} dependency new dependency
*/
- addDependency: function (dependency) {
- var dependentNode = dependency.getDependentNode();
+ addDependency(dependency) {
+ const dependentNode = dependency.getDependentNode();
if (!_nodeDependencies.has(dependentNode.id)) {
_nodeDependencies.set(dependentNode.id, [dependency]);
}
else {
_nodeDependencies.get(dependentNode.id).push(dependency);
}
- var fields = dependency.getFields();
- for (var i = 0, length = fields.length; i < length; i++) {
- var field = fields[i];
- var id = DomUtil.identify(field);
+ dependency.getFields().forEach((field) => {
+ const id = Util_1.default.identify(field);
if (!_fields.has(id)) {
_fields.set(id, field);
- if (field.tagName === 'INPUT' && (field.type === 'checkbox' || field.type === 'radio' || field.type === 'hidden')) {
- field.addEventListener('change', this.checkDependencies.bind(this));
+ if (field.tagName === "INPUT" &&
+ (field.type === "checkbox" ||
+ field.type === "radio" ||
+ field.type === "hidden")) {
+ field.addEventListener("change", () => this.checkDependencies());
}
else {
- field.addEventListener('input', this.checkDependencies.bind(this));
+ field.addEventListener("input", () => this.checkDependencies());
}
}
- }
- },
- /**
- * Checks if all dependencies are met.
- */
- checkDependencies: function () {
- var obsoleteNodeIds = [];
- _nodeDependencies.forEach(function (nodeDependencies, nodeId) {
- var dependentNode = elById(nodeId);
- // check if dependent node still exists
- if (dependentNode === null) {
- obsoleteNodeIds.push(nodeId);
- return;
- }
- for (var i = 0, length = nodeDependencies.length; i < length; i++) {
- // if any dependency is not met, hide the element
- if (!nodeDependencies[i].checkDependency()) {
- this._hide(dependentNode);
- return;
- }
- }
- // all node dependency is met
- this._show(dependentNode);
- }.bind(this));
- // delete dependencies for removed elements
- for (var i = 0, length = obsoleteNodeIds.length; i < length; i++) {
- _nodeDependencies.delete(obsoleteNodeIds[i]);
- }
- this.checkContainers();
- },
- /**
- * Adds the given callback to the list of callbacks called when checking containers.
- *
- * @param {function} callback
- */
- addContainerCheckCallback: function (callback) {
- if (typeof callback !== 'function') {
- throw new TypeError("Expected a valid callback for parameter 'callback'.");
- }
- EventHandler.add('com.woltlab.wcf.form.builder.dependency', 'checkContainers', callback);
+ });
},
/**
* Checks the containers for their availability.
* If this function is called while containers are currently checked, the containers
* will be checked after the current check has been finished completely.
*/
- checkContainers: function () {
+ checkContainers() {
// check if containers are currently being checked
if (_checkingContainers === true) {
// and if that is the case, calling this method indicates, that after the current round,
_checkContainersAgain = true;
return;
}
- // starting to check containers also resets the flag to check containers again after the current check
+ // starting to check containers also resets the flag to check containers again after the current check
_checkingContainers = true;
_checkContainersAgain = false;
- EventHandler.fire('com.woltlab.wcf.form.builder.dependency', 'checkContainers');
+ EventHandler.fire("com.woltlab.wcf.form.builder.dependency", "checkContainers");
// finish checking containers and check if containters should be checked again
_checkingContainers = false;
if (_checkContainersAgain) {
this.checkContainers();
}
},
+ /**
+ * Checks if all dependencies are met.
+ */
+ checkDependencies() {
+ const obsoleteNodeIds = [];
+ _nodeDependencies.forEach((nodeDependencies, nodeId) => {
+ const dependentNode = document.getElementById(nodeId);
+ if (dependentNode === null) {
+ obsoleteNodeIds.push(nodeId);
+ return;
+ }
+ let dependenciesMet = true;
+ nodeDependencies.forEach((dependency) => {
+ if (!dependency.checkDependency()) {
+ dependentNode.style.display = "none";
+ dependenciesMet = false;
+ }
+ });
+ if (dependenciesMet) {
+ this._show(dependentNode);
+ }
+ });
+ obsoleteNodeIds.forEach((id) => _nodeDependencies.delete(id));
+ this.checkContainers();
+ },
/**
* Returns `true` if the given node has been hidden because of its own dependencies.
- *
- * @param {HTMLElement} node checked node
- * @return {boolean}
*/
- isHiddenByDependencies: function (node) {
+ isHiddenByDependencies(node) {
if (_dependencyHiddenNodes.has(node)) {
return true;
}
- var returnValue = false;
- _dependencyHiddenNodes.forEach(function (hiddenNode) {
- if (DomUtil.contains(hiddenNode, node)) {
+ let returnValue = false;
+ _dependencyHiddenNodes.forEach((hiddenNode) => {
+ if (node.contains(hiddenNode)) {
returnValue = true;
}
});
},
/**
* Registers the form with the given id with the dependency manager.
- *
- * @param {string} formId id of register form
- * @throws {Error} if given form id is invalid or has already been registered
*/
- register: function (formId) {
- var form = elById(formId);
+ register(formId) {
+ const form = document.getElementById(formId);
if (form === null) {
throw new Error("Unknown element with id '" + formId + "'");
}
},
/**
* Unregisters the form with the given id and all of its dependencies.
- *
- * @param {string} formId id of unregistered form
*/
- unregister: function (formId) {
- var form = elById(formId);
+ unregister(formId) {
+ const form = document.getElementById(formId);
if (form === null) {
throw new Error("Unknown element with id '" + formId + "'");
}
throw new Error("Form with id '" + formId + "' has not been registered.");
}
_forms.delete(form);
- _dependencyHiddenNodes.forEach(function (hiddenNode) {
+ _dependencyHiddenNodes.forEach((hiddenNode) => {
if (form.contains(hiddenNode)) {
_dependencyHiddenNodes.delete(hiddenNode);
}
});
- _nodeDependencies.forEach(function (dependencies, nodeId) {
- if (form.contains(elById(nodeId))) {
+ _nodeDependencies.forEach((dependencies, nodeId) => {
+ if (form.contains(document.getElementById(nodeId))) {
_nodeDependencies.delete(nodeId);
}
- for (var i = 0, length = dependencies.length; i < length; i++) {
- var fields = dependencies[i].getFields();
- for (var j = 0, fieldsLength = fields.length; j < fieldsLength; j++) {
- var field = fields[j];
+ dependencies.forEach((dependency) => {
+ dependency.getFields().forEach((field) => {
_fields.delete(field.id);
_validatedFieldProperties.delete(field);
- }
- }
+ });
+ });
});
- }
+ },
};
+ Core.enableLegacyInheritance(Manager);
+ return Manager;
});
+++ /dev/null
-/**
- * Manages form field dependencies.
- *
- * @author Matthias Schmidt
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager
- * @since 5.2
- */
-define(['Dictionary', 'Dom/ChangeListener', 'EventHandler', 'List', 'Dom/Util', 'ObjectMap'], function(Dictionary, DomChangeListener, EventHandler, List, DomUtil, ObjectMap) {
- "use strict";
-
- /**
- * is `true` if containters are currently checked for their availablility, otherwise `false`
- * @type {boolean}
- * @private
- */
- var _checkingContainers = false;
-
- /**
- * is `true` if containter will be checked again after the current check for their availablility
- * has finished, otherwise `false`
- * @type {boolean}
- * @private
- */
- var _checkContainersAgain = true;
-
- /**
- * list of containers hidden due to their own dependencies
- * @type {List}
- * @private
- */
- var _dependencyHiddenNodes = new List();
-
- /**
- * list of fields for which event listeners have been registered
- * @type {Dictionary}
- * @private
- */
- var _fields = new Dictionary();
-
- /**
- * list of registered forms
- * @type {List}
- * @private
- */
- var _forms = new List();
-
- /**
- * list of dependencies grouped by the dependent node they belong to
- * @type {Dictionary}
- * @private
- */
- var _nodeDependencies = new Dictionary();
-
- /**
- * cache of validation-related properties of hidden form fields
- * @type {ObjectMap}
- * @private
- */
- var _validatedFieldProperties = new ObjectMap();
-
- return {
- /**
- * Hides the given node because of its own dependencies.
- *
- * @param {HTMLElement} node hidden node
- * @protected
- */
- _hide: function(node) {
- elHide(node);
- _dependencyHiddenNodes.add(node);
-
- // also hide tab menu entry
- if (node.classList.contains('tabMenuContent')) {
- elBySelAll('li', node.parentNode.querySelector('.tabMenu'), function(tabLink) {
- if (elData(tabLink, 'name') === elData(node, 'name')) {
- elHide(tabLink);
- }
- });
- }
-
- elBySelAll('[max], [maxlength], [min], [required]', node, function(validatedField) {
- var properties = new Dictionary();
-
- var max = elAttr(validatedField, 'max');
- if (max) {
- properties.set('max', max);
- validatedField.removeAttribute('max');
- }
-
- var maxlength = elAttr(validatedField, 'maxlength');
- if (maxlength) {
- properties.set('maxlength', maxlength);
- validatedField.removeAttribute('maxlength');
- }
-
- var min = elAttr(validatedField, 'min');
- if (min) {
- properties.set('min', min);
- validatedField.removeAttribute('min');
- }
-
- if (validatedField.required) {
- properties.set('required', true);
- validatedField.removeAttribute('required');
- }
-
- _validatedFieldProperties.set(validatedField, properties);
- });
- },
-
- /**
- * Shows the given node because of its own dependencies.
- *
- * @param {HTMLElement} node shown node
- * @protected
- */
- _show: function(node) {
- elShow(node);
- _dependencyHiddenNodes.delete(node);
-
- // also show tab menu entry
- if (node.classList.contains('tabMenuContent')) {
- elBySelAll('li', node.parentNode.querySelector('.tabMenu'), function(tabLink) {
- if (elData(tabLink, 'name') === elData(node, 'name')) {
- elShow(tabLink);
- }
- });
- }
-
- elBySelAll('input, select', node, function(validatedField) {
- // if a container is shown, ignore all fields that
- // have a hidden parent element within the container
- var parentNode = validatedField.parentNode;
- while (parentNode !== node && parentNode.style.getPropertyValue('display') !== 'none') {
- parentNode = parentNode.parentNode;
- }
-
- if (parentNode === node && _validatedFieldProperties.has(validatedField)) {
- var properties = _validatedFieldProperties.get(validatedField);
-
- if (properties.has('max')) {
- elAttr(validatedField, 'max', properties.get('max'));
- }
- if (properties.has('maxlength')) {
- elAttr(validatedField, 'maxlength', properties.get('maxlength'));
- }
- if (properties.has('min')) {
- elAttr(validatedField, 'min', properties.get('min'));
- }
- if (properties.has('required')) {
- elAttr(validatedField, 'required', '');
- }
-
- _validatedFieldProperties.delete(validatedField);
- }
- });
- },
-
- /**
- * Registers a new form field dependency.
- *
- * @param {WoltLabSuite/Core/Form/Builder/Field/Dependency/Abstract} dependency new dependency
- */
- addDependency: function(dependency) {
- var dependentNode = dependency.getDependentNode();
- if (!_nodeDependencies.has(dependentNode.id)) {
- _nodeDependencies.set(dependentNode.id, [dependency]);
- }
- else {
- _nodeDependencies.get(dependentNode.id).push(dependency);
- }
-
- var fields = dependency.getFields();
- for (var i = 0, length = fields.length; i < length; i++) {
- var field = fields[i];
- var id = DomUtil.identify(field);
-
- if (!_fields.has(id)) {
- _fields.set(id, field);
-
- if (field.tagName === 'INPUT' && (field.type === 'checkbox' || field.type === 'radio' || field.type === 'hidden')) {
- field.addEventListener('change', this.checkDependencies.bind(this));
- }
- else {
- field.addEventListener('input', this.checkDependencies.bind(this));
- }
- }
- }
- },
-
- /**
- * Checks if all dependencies are met.
- */
- checkDependencies: function() {
- var obsoleteNodeIds = [];
-
- _nodeDependencies.forEach(function(nodeDependencies, nodeId) {
- var dependentNode = elById(nodeId);
-
- // check if dependent node still exists
- if (dependentNode === null) {
- obsoleteNodeIds.push(nodeId);
- return;
- }
-
- for (var i = 0, length = nodeDependencies.length; i < length; i++) {
- // if any dependency is not met, hide the element
- if (!nodeDependencies[i].checkDependency()) {
- this._hide(dependentNode);
- return;
- }
- }
-
- // all node dependency is met
- this._show(dependentNode);
- }.bind(this));
-
- // delete dependencies for removed elements
- for (var i = 0, length = obsoleteNodeIds.length; i < length; i++) {
- _nodeDependencies.delete(obsoleteNodeIds[i]);
- }
-
- this.checkContainers();
- },
-
- /**
- * Adds the given callback to the list of callbacks called when checking containers.
- *
- * @param {function} callback
- */
- addContainerCheckCallback: function(callback) {
- if (typeof callback !== 'function') {
- throw new TypeError("Expected a valid callback for parameter 'callback'.");
- }
-
- EventHandler.add('com.woltlab.wcf.form.builder.dependency', 'checkContainers', callback);
- },
-
- /**
- * Checks the containers for their availability.
- *
- * If this function is called while containers are currently checked, the containers
- * will be checked after the current check has been finished completely.
- */
- checkContainers: function() {
- // check if containers are currently being checked
- if (_checkingContainers === true) {
- // and if that is the case, calling this method indicates, that after the current round,
- // containters should be checked to properly propagate changes in children to their parents
- _checkContainersAgain = true;
-
- return;
- }
-
- // starting to check containers also resets the flag to check containers again after the current check
- _checkingContainers = true;
- _checkContainersAgain = false;
-
- EventHandler.fire('com.woltlab.wcf.form.builder.dependency', 'checkContainers');
-
- // finish checking containers and check if containters should be checked again
- _checkingContainers = false;
- if (_checkContainersAgain) {
- this.checkContainers();
- }
- },
-
- /**
- * Returns `true` if the given node has been hidden because of its own dependencies.
- *
- * @param {HTMLElement} node checked node
- * @return {boolean}
- */
- isHiddenByDependencies: function(node) {
- if (_dependencyHiddenNodes.has(node)) {
- return true;
- }
-
- var returnValue = false;
- _dependencyHiddenNodes.forEach(function(hiddenNode) {
- if (DomUtil.contains(hiddenNode, node)) {
- returnValue = true;
- }
- });
-
- return returnValue;
- },
-
- /**
- * Registers the form with the given id with the dependency manager.
- *
- * @param {string} formId id of register form
- * @throws {Error} if given form id is invalid or has already been registered
- */
- register: function(formId) {
- var form = elById(formId);
-
- if (form === null) {
- throw new Error("Unknown element with id '" + formId + "'");
- }
-
- if (_forms.has(form)) {
- throw new Error("Form with id '" + formId + "' has already been registered.");
- }
-
- _forms.add(form);
- },
-
- /**
- * Unregisters the form with the given id and all of its dependencies.
- *
- * @param {string} formId id of unregistered form
- */
- unregister: function(formId) {
- var form = elById(formId);
-
- if (form === null) {
- throw new Error("Unknown element with id '" + formId + "'");
- }
-
- if (!_forms.has(form)) {
- throw new Error("Form with id '" + formId + "' has not been registered.");
- }
-
- _forms.delete(form);
-
- _dependencyHiddenNodes.forEach(function(hiddenNode) {
- if (form.contains(hiddenNode)) {
- _dependencyHiddenNodes.delete(hiddenNode);
- }
- });
- _nodeDependencies.forEach(function(dependencies, nodeId) {
- if (form.contains(elById(nodeId))) {
- _nodeDependencies.delete(nodeId);
- }
-
- for (var i = 0, length = dependencies.length; i < length; i++) {
- var fields = dependencies[i].getFields();
- for (var j = 0, fieldsLength = fields.length; j < fieldsLength; j++) {
- var field = fields[j];
-
- _fields.delete(field.id);
-
- _validatedFieldProperties.delete(field);
- }
- }
- });
- }
- };
-});
--- /dev/null
+/**
+ * Manages form field dependencies.
+ *
+ * @author Matthias Schmidt
+ * @copyright 2001-2020 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager
+ * @since 5.2
+ */
+
+import DomUtil from "../../../../Dom/Util";
+import * as EventHandler from "../../../../Event/Handler";
+import FormBuilderFormFieldDependency from "./Abstract";
+import * as DomTraverse from "../../../../Dom/Traverse";
+import * as Core from "../../../../Core";
+
+type PropertiesMap = Map<string, string>;
+
+const _dependencyHiddenNodes = new Set<HTMLElement>();
+const _fields = new Map<string, HTMLElement>();
+const _forms = new Set<HTMLElement>();
+const _nodeDependencies = new Map<string, FormBuilderFormFieldDependency[]>();
+const _validatedFieldProperties = new WeakMap<HTMLElement, PropertiesMap>();
+
+let _checkingContainers = false;
+let _checkContainersAgain = true;
+
+type Callback = (...args: any[]) => void;
+
+const Manager = {
+ /**
+ * Hides the given node because of its own dependencies.
+ */
+ _hide(node: HTMLElement): void {
+ node.style.display = "none";
+ _dependencyHiddenNodes.add(node);
+
+ // also hide tab menu entry
+ if (node.classList.contains("tabMenuContent")) {
+ node
+ .parentNode!.querySelector(".tabMenu")!
+ .querySelectorAll("li")
+ .forEach((tabLink) => {
+ if (tabLink.dataset.name === node.dataset.name) {
+ tabLink.style.display = "none";
+ }
+ });
+ }
+
+ node.querySelectorAll("[max], [maxlength], [min], [required]").forEach((validatedField: HTMLInputElement) => {
+ const properties = new Map<string, string>();
+
+ const max = validatedField.getAttribute("max");
+ if (max) {
+ properties.set("max", max);
+ validatedField.removeAttribute("max");
+ }
+
+ const maxlength = validatedField.getAttribute("maxlength");
+ if (maxlength) {
+ properties.set("maxlength", maxlength);
+ validatedField.removeAttribute("maxlength");
+ }
+
+ const min = validatedField.getAttribute("min");
+ if (min) {
+ properties.set("min", min);
+ validatedField.removeAttribute("min");
+ }
+
+ if (validatedField.required) {
+ properties.set("required", "true");
+ validatedField.removeAttribute("required");
+ }
+
+ _validatedFieldProperties.set(validatedField, properties);
+ });
+ },
+
+ /**
+ * Shows the given node because of its own dependencies.
+ */
+ _show(node: HTMLElement): void {
+ node.style.display = "block";
+ _dependencyHiddenNodes.delete(node);
+
+ // also show tab menu entry
+ if (node.classList.contains("tabMenuContent")) {
+ node
+ .parentNode!.querySelector(".tabMenu")!
+ .querySelectorAll("li")
+ .forEach((tabLink) => {
+ if (tabLink.dataset.name === node.dataset.name) {
+ tabLink.style.display = "block";
+ }
+ });
+ }
+
+ node.querySelectorAll("input, select").forEach((validatedField: HTMLInputElement | HTMLSelectElement) => {
+ // if a container is shown, ignore all fields that
+ // have a hidden parent element within the container
+ let parentNode = validatedField.parentNode! as HTMLElement;
+ while (parentNode !== node && parentNode.style.getPropertyValue("display") !== "none") {
+ parentNode = parentNode.parentNode! as HTMLElement;
+ }
+
+ if (parentNode === node && _validatedFieldProperties.has(validatedField)) {
+ const properties = _validatedFieldProperties.get(validatedField)!;
+
+ if (properties.has("max")) {
+ validatedField.setAttribute("max", properties.get("max")!);
+ }
+ if (properties.has("maxlength")) {
+ validatedField.setAttribute("maxlength", properties.get("maxlength")!);
+ }
+ if (properties.has("min")) {
+ validatedField.setAttribute("min", properties.get("min")!);
+ }
+ if (properties.has("required")) {
+ validatedField.setAttribute("required", "");
+ }
+
+ _validatedFieldProperties.delete(validatedField);
+ }
+ });
+ },
+
+ /**
+ * Adds the given callback to the list of callbacks called when checking containers.
+ */
+ addContainerCheckCallback(callback: Callback): void {
+ if (typeof callback !== "function") {
+ throw new TypeError("Expected a valid callback for parameter 'callback'.");
+ }
+
+ EventHandler.add("com.woltlab.wcf.form.builder.dependency", "checkContainers", callback);
+ },
+
+ /**
+ * Registers a new form field dependency.
+ */
+ addDependency(dependency: FormBuilderFormFieldDependency): void {
+ const dependentNode = dependency.getDependentNode();
+ if (!_nodeDependencies.has(dependentNode.id)) {
+ _nodeDependencies.set(dependentNode.id, [dependency]);
+ } else {
+ _nodeDependencies.get(dependentNode.id)!.push(dependency);
+ }
+
+ dependency.getFields().forEach((field) => {
+ const id = DomUtil.identify(field);
+
+ if (!_fields.has(id)) {
+ _fields.set(id, field);
+
+ if (
+ field.tagName === "INPUT" &&
+ ((field as HTMLInputElement).type === "checkbox" ||
+ (field as HTMLInputElement).type === "radio" ||
+ (field as HTMLInputElement).type === "hidden")
+ ) {
+ field.addEventListener("change", () => this.checkDependencies());
+ } else {
+ field.addEventListener("input", () => this.checkDependencies());
+ }
+ }
+ });
+ },
+
+ /**
+ * Checks the containers for their availability.
+ *
+ * If this function is called while containers are currently checked, the containers
+ * will be checked after the current check has been finished completely.
+ */
+ checkContainers(): void {
+ // check if containers are currently being checked
+ if (_checkingContainers === true) {
+ // and if that is the case, calling this method indicates, that after the current round,
+ // containters should be checked to properly propagate changes in children to their parents
+ _checkContainersAgain = true;
+
+ return;
+ }
+
+ // starting to check containers also resets the flag to check containers again after the current check
+ _checkingContainers = true;
+ _checkContainersAgain = false;
+
+ EventHandler.fire("com.woltlab.wcf.form.builder.dependency", "checkContainers");
+
+ // finish checking containers and check if containters should be checked again
+ _checkingContainers = false;
+ if (_checkContainersAgain) {
+ this.checkContainers();
+ }
+ },
+
+ /**
+ * Checks if all dependencies are met.
+ */
+ checkDependencies(): void {
+ const obsoleteNodeIds: string[] = [];
+
+ _nodeDependencies.forEach((nodeDependencies, nodeId) => {
+ const dependentNode = document.getElementById(nodeId);
+ if (dependentNode === null) {
+ obsoleteNodeIds.push(nodeId);
+
+ return;
+ }
+
+ let dependenciesMet = true;
+ nodeDependencies.forEach((dependency) => {
+ if (!dependency.checkDependency()) {
+ dependentNode.style.display = "none";
+ dependenciesMet = false;
+ }
+ });
+
+ if (dependenciesMet) {
+ this._show(dependentNode);
+ }
+ });
+
+ obsoleteNodeIds.forEach((id) => _nodeDependencies.delete(id));
+
+ this.checkContainers();
+ },
+
+ /**
+ * Returns `true` if the given node has been hidden because of its own dependencies.
+ */
+ isHiddenByDependencies(node: HTMLElement): boolean {
+ if (_dependencyHiddenNodes.has(node)) {
+ return true;
+ }
+
+ let returnValue = false;
+ _dependencyHiddenNodes.forEach((hiddenNode) => {
+ if (node.contains(hiddenNode)) {
+ returnValue = true;
+ }
+ });
+
+ return returnValue;
+ },
+
+ /**
+ * Registers the form with the given id with the dependency manager.
+ */
+ register(formId: string): void {
+ const form = document.getElementById(formId);
+
+ if (form === null) {
+ throw new Error("Unknown element with id '" + formId + "'");
+ }
+
+ if (_forms.has(form)) {
+ throw new Error("Form with id '" + formId + "' has already been registered.");
+ }
+
+ _forms.add(form);
+ },
+
+ /**
+ * Unregisters the form with the given id and all of its dependencies.
+ */
+ unregister(formId: string): void {
+ const form = document.getElementById(formId);
+
+ if (form === null) {
+ throw new Error("Unknown element with id '" + formId + "'");
+ }
+
+ if (!_forms.has(form)) {
+ throw new Error("Form with id '" + formId + "' has not been registered.");
+ }
+
+ _forms.delete(form);
+
+ _dependencyHiddenNodes.forEach((hiddenNode) => {
+ if (form.contains(hiddenNode)) {
+ _dependencyHiddenNodes.delete(hiddenNode);
+ }
+ });
+ _nodeDependencies.forEach((dependencies, nodeId) => {
+ if (form.contains(document.getElementById(nodeId))) {
+ _nodeDependencies.delete(nodeId);
+ }
+
+ dependencies.forEach((dependency) => {
+ dependency.getFields().forEach((field) => {
+ _fields.delete(field.id);
+
+ _validatedFieldProperties.delete(field);
+ });
+ });
+ });
+ },
+};
+
+Core.enableLegacyInheritance(Manager);
+
+export = Manager;