diff --git a/development/issues/closed/292.html b/development/issues/closed/292.html new file mode 100644 index 0000000000000000000000000000000000000000..f7f19bb325257d2a8f165f83628099e85f6d2f58 --- /dev/null +++ b/development/issues/closed/292.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>new transformer command for lists #292</title> + <script src="./292.mjs" type="module"></script> +</head> + +<body> + <h1>new transformer command for lists #292</h1> + <p></p> + <ul> + <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/292">Issue #292</a></li> + <li><a href="/">Back to overview</a></li> + </ul> + <main> + + <p> look at the console for the output</p> + + <!-- Write your code here --> + + </main> +</body> + +</html> diff --git a/development/issues/closed/292.mjs b/development/issues/closed/292.mjs new file mode 100644 index 0000000000000000000000000000000000000000..9036e8c5881709da95830f6305ae4748e550cd27 --- /dev/null +++ b/development/issues/closed/292.mjs @@ -0,0 +1,26 @@ +/** +* @file development/issues/open/292.mjs +* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/292 +* @description new transformer command for lists +* @issue 292 +*/ + +import "../../../source/components/style/property.pcss"; +import "../../../source/components/style/link.pcss"; +import "../../../source/components/style/color.pcss"; +import "../../../source/components/style/theme.pcss"; +import "../../../source/components/style/normalize.pcss"; +import "../../../source/components/style/typography.pcss"; +import {Transformer} from "../../../source/data/transformer.mjs"; + + +const transformer = new Transformer("listtoarray"); +console.log(transformer.run("1,2,3,4")); + + +const transformer2 = new Transformer("arraytolist"); +console.log(transformer2.run([1,2,3,4])); + + +const transformer3 = new Transformer("default::array"); +console.log(transformer3.run(undefined)); diff --git a/source/data/transformer.mjs b/source/data/transformer.mjs index caeb744fa6f0a4c5de3f8ce440b42995aa29f430..ca88d015401b1e9664aa4a2991a63b56af36eece 100644 --- a/source/data/transformer.mjs +++ b/source/data/transformer.mjs @@ -23,6 +23,7 @@ import { } from "../i18n/translations.mjs"; import { validateFunction, + validateArray, validateInteger, validateObject, validatePrimitive, @@ -286,6 +287,30 @@ function transform(value) { validateInteger(n); return n; + case "to-array": + case "toarray": + if (isArray(value)) { + return value; + } + + if (isObject(value)) { + return Object.values(value); + } + + return [value]; + + case "listtoarray": + case "list-to-array": + validateString(value); + const listDel = args.shift() || ","; + return value.split(listDel); + + case "arraytolist": + case "array-to-list": + validateArray(value); + const listDel2 = args.shift() || ","; + return value.join(listDel2); + case "to-json": case "tojson": return JSON.stringify(value); @@ -398,7 +423,10 @@ function transform(value) { case "debug": if (isObject(console)) { - console.log(value); + console.groupCollapsed("Transformer Debug"); + console.log("Value", value); + console.log("Transformer", this); + console.groupEnd(); } return value; @@ -581,6 +609,11 @@ function transform(value) { defaultValue === "true" || defaultValue === "true" ); + case "array": + if (defaultValue === "") { + return []; + } + return defaultValue.split(","); case "string": return `${defaultValue}`; case "object": diff --git a/source/dom/updater.mjs b/source/dom/updater.mjs index 303874215f536065782bd34b9af891bd5242051f..5aa2ce0f805d17f33dad1d2c8173324bd6cc95a0 100644 --- a/source/dom/updater.mjs +++ b/source/dom/updater.mjs @@ -12,43 +12,47 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {internalSymbol} from "../constants.mjs"; -import {diff} from "../data/diff.mjs"; -import {Pathfinder} from "../data/pathfinder.mjs"; -import {Pipe} from "../data/pipe.mjs"; +import { internalSymbol } from "../constants.mjs"; +import { diff } from "../data/diff.mjs"; +import { Pathfinder } from "../data/pathfinder.mjs"; +import { Pipe } from "../data/pipe.mjs"; import { - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_UPDATER_ATTRIBUTES, - ATTRIBUTE_UPDATER_BIND, - ATTRIBUTE_UPDATER_BIND_TYPE, - ATTRIBUTE_UPDATER_INSERT, - ATTRIBUTE_UPDATER_INSERT_REFERENCE, - ATTRIBUTE_UPDATER_REMOVE, - ATTRIBUTE_UPDATER_REPLACE, - ATTRIBUTE_UPDATER_SELECT_THIS, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_UPDATER_ATTRIBUTES, + ATTRIBUTE_UPDATER_BIND, + ATTRIBUTE_UPDATER_BIND_TYPE, + ATTRIBUTE_UPDATER_INSERT, + ATTRIBUTE_UPDATER_INSERT_REFERENCE, + ATTRIBUTE_UPDATER_REMOVE, + ATTRIBUTE_UPDATER_REPLACE, + ATTRIBUTE_UPDATER_SELECT_THIS, } from "./constants.mjs"; -import {Base} from "../types/base.mjs"; -import {isArray, isInteger, isString, isInstance, isIterable} from "../types/is.mjs"; -import {Observer} from "../types/observer.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateArray, validateInstance} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; -import {trimSpaces} from "../util/trimspaces.mjs"; -import {addAttributeToken, addToObjectLink} from "./attributes.mjs"; +import { Base } from "../types/base.mjs"; import { - CustomElement, - updaterTransformerMethodsSymbol, + isArray, + isInteger, + isString, + isInstance, + isIterable, +} from "../types/is.mjs"; +import { Observer } from "../types/observer.mjs"; +import { ProxyObserver } from "../types/proxyobserver.mjs"; +import { validateArray, validateInstance } from "../types/validate.mjs"; +import { clone } from "../util/clone.mjs"; +import { trimSpaces } from "../util/trimspaces.mjs"; +import { addAttributeToken, addToObjectLink } from "./attributes.mjs"; +import { + CustomElement, + updaterTransformerMethodsSymbol, } from "./customelement.mjs"; -import {findTargetElementFromEvent} from "./events.mjs"; -import {findDocumentTemplate} from "./template.mjs"; -import {getWindow} from "./util.mjs"; -import {DeadMansSwitch} from "../util/deadmansswitch.mjs"; -import {addErrorAttribute, removeErrorAttribute} from "./error.mjs"; - - -export {Updater, addObjectWithUpdaterToElement}; +import { findTargetElementFromEvent } from "./events.mjs"; +import { findDocumentTemplate } from "./template.mjs"; +import { getWindow } from "./util.mjs"; +import { DeadMansSwitch } from "../util/deadmansswitch.mjs"; +import { addErrorAttribute, removeErrorAttribute } from "./error.mjs"; +export { Updater, addObjectWithUpdaterToElement }; /** * @private @@ -81,190 +85,190 @@ const timerElementEventHandlerSymbol = Symbol("timerElementEventHandler"); * @summary The updater class connects an object with the dom */ class Updater extends Base { - /** - * @since 1.8.0 - * @param {HTMLElement} element - * @param {object|ProxyObserver|undefined} subject - * @throws {TypeError} value is not a object - * @throws {TypeError} value is not an instance of HTMLElement - * @see {@link findDocumentTemplate} - */ - constructor(element, subject) { - super(); - - /** - * @type {HTMLElement} - */ - if (subject === undefined) subject = {}; - if (!isInstance(subject, ProxyObserver)) { - subject = new ProxyObserver(subject); - } - - this[internalSymbol] = { - element: validateInstance(element, HTMLElement), - last: {}, - callbacks: new Map(), - eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"], - subject: subject, - }; - - this[internalSymbol].callbacks.set( - "checkstate", - getCheckStateCallback.call(this), - ); - - this[internalSymbol].subject.attachObserver( - new Observer(() => { - const s = this[internalSymbol].subject.getRealSubject(); - - const diffResult = diff(this[internalSymbol].last, s); - this[internalSymbol].last = clone(s); - - const promises = []; - - for (const [, change] of Object.entries(diffResult)) { - promises.push( - new Promise((resolve, reject) => { - getWindow().requestAnimationFrame(() => { - try { - removeElement.call(this, change); - insertElement.call(this, change); - updateContent.call(this, change); - updateAttributes.call(this, change); - - resolve(); - } catch (error) { - reject(error); - } - }); - }), - ); - } - - return Promise.all(promises); - }), - ); - } - - /** - * Defaults: 'keyup', 'click', 'change', 'drop', 'touchend' - * - * @see {@link https://developer.mozilla.org/de/docs/Web/Events} - * @since 1.9.0 - * @param {Array} types - * @return {Updater} - */ - setEventTypes(types) { - this[internalSymbol].eventTypes = validateArray(types); - return this; - } - - /** - * With this method, the eventlisteners are hooked in and the magic begins. - * - * ```js - * updater.run().then(() => { - * updater.enableEventProcessing(); - * }); - * ``` - * - * @since 1.9.0 - * @return {Updater} - * @throws {Error} the bind argument must start as a value with a path - */ - enableEventProcessing() { - this.disableEventProcessing(); - - for (const type of this[internalSymbol].eventTypes) { - // @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - this[internalSymbol].element.addEventListener( - type, - getControlEventHandler.call(this), - { - capture: true, - passive: true, - }, - ); - } - - return this; - } - - /** - * This method turns off the magic or who loves it more profane it removes the eventListener. - * - * @since 1.9.0 - * @return {Updater} - */ - disableEventProcessing() { - for (const type of this[internalSymbol].eventTypes) { - this[internalSymbol].element.removeEventListener( - type, - getControlEventHandler.call(this), - ); - } - - return this; - } - - /** - * The run method must be called for the update to start working. - * The method ensures that changes are detected. - * - * ```js - * updater.run().then(() => { - * updater.enableEventProcessing(); - * }); - * ``` - * - * @summary Let the magic begin - * @return {Promise} - */ - run() { - // the key __init__has no further meaning and is only - // used to create the diff for empty objects. - this[internalSymbol].last = {__init__: true}; - return this[internalSymbol].subject.notifyObservers(); - } - - /** - * Gets the values of bound elements and changes them in subject - * - * @since 1.27.0 - * @return {Monster.DOM.Updater} - */ - retrieve() { - retrieveFromBindings.call(this); - return this; - } - - /** - * If you have passed a ProxyObserver in the constructor, you will get the object that the ProxyObserver manages here. - * However, if you passed a simple object, here you will get a proxy for that object. - * - * For changes, the ProxyObserver must be used. - * - * @since 1.8.0 - * @return {Proxy} - */ - getSubject() { - return this[internalSymbol].subject.getSubject(); - } - - /** - * This method can be used to register commands that can be called via call: instruction. - * This can be used to provide a pipe with its own functionality. - * - * @param {string} name - * @param {function} callback - * @return {Transformer} - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not a function - */ - setCallback(name, callback) { - this[internalSymbol].callbacks.set(name, callback); - return this; - } + /** + * @since 1.8.0 + * @param {HTMLElement} element + * @param {object|ProxyObserver|undefined} subject + * @throws {TypeError} value is not a object + * @throws {TypeError} value is not an instance of HTMLElement + * @see {@link findDocumentTemplate} + */ + constructor(element, subject) { + super(); + + /** + * @type {HTMLElement} + */ + if (subject === undefined) subject = {}; + if (!isInstance(subject, ProxyObserver)) { + subject = new ProxyObserver(subject); + } + + this[internalSymbol] = { + element: validateInstance(element, HTMLElement), + last: {}, + callbacks: new Map(), + eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"], + subject: subject, + }; + + this[internalSymbol].callbacks.set( + "checkstate", + getCheckStateCallback.call(this), + ); + + this[internalSymbol].subject.attachObserver( + new Observer(() => { + const s = this[internalSymbol].subject.getRealSubject(); + + const diffResult = diff(this[internalSymbol].last, s); + this[internalSymbol].last = clone(s); + + const promises = []; + + for (const [, change] of Object.entries(diffResult)) { + promises.push( + new Promise((resolve, reject) => { + getWindow().requestAnimationFrame(() => { + try { + removeElement.call(this, change); + insertElement.call(this, change); + updateContent.call(this, change); + updateAttributes.call(this, change); + + resolve(); + } catch (error) { + reject(error); + } + }); + }), + ); + } + + return Promise.all(promises); + }), + ); + } + + /** + * Defaults: 'keyup', 'click', 'change', 'drop', 'touchend' + * + * @see {@link https://developer.mozilla.org/de/docs/Web/Events} + * @since 1.9.0 + * @param {Array} types + * @return {Updater} + */ + setEventTypes(types) { + this[internalSymbol].eventTypes = validateArray(types); + return this; + } + + /** + * With this method, the eventlisteners are hooked in and the magic begins. + * + * ```js + * updater.run().then(() => { + * updater.enableEventProcessing(); + * }); + * ``` + * + * @since 1.9.0 + * @return {Updater} + * @throws {Error} the bind argument must start as a value with a path + */ + enableEventProcessing() { + this.disableEventProcessing(); + + for (const type of this[internalSymbol].eventTypes) { + // @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + this[internalSymbol].element.addEventListener( + type, + getControlEventHandler.call(this), + { + capture: true, + passive: true, + }, + ); + } + + return this; + } + + /** + * This method turns off the magic or who loves it more profane it removes the eventListener. + * + * @since 1.9.0 + * @return {Updater} + */ + disableEventProcessing() { + for (const type of this[internalSymbol].eventTypes) { + this[internalSymbol].element.removeEventListener( + type, + getControlEventHandler.call(this), + ); + } + + return this; + } + + /** + * The run method must be called for the update to start working. + * The method ensures that changes are detected. + * + * ```js + * updater.run().then(() => { + * updater.enableEventProcessing(); + * }); + * ``` + * + * @summary Let the magic begin + * @return {Promise} + */ + run() { + // the key __init__has no further meaning and is only + // used to create the diff for empty objects. + this[internalSymbol].last = { __init__: true }; + return this[internalSymbol].subject.notifyObservers(); + } + + /** + * Gets the values of bound elements and changes them in subject + * + * @since 1.27.0 + * @return {Monster.DOM.Updater} + */ + retrieve() { + retrieveFromBindings.call(this); + return this; + } + + /** + * If you have passed a ProxyObserver in the constructor, you will get the object that the ProxyObserver manages here. + * However, if you passed a simple object, here you will get a proxy for that object. + * + * For changes, the ProxyObserver must be used. + * + * @since 1.8.0 + * @return {Proxy} + */ + getSubject() { + return this[internalSymbol].subject.getSubject(); + } + + /** + * This method can be used to register commands that can be called via call: instruction. + * This can be used to provide a pipe with its own functionality. + * + * @param {string} name + * @param {function} callback + * @return {Transformer} + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not a function + */ + setCallback(name, callback) { + this[internalSymbol].callbacks.set(name, callback); + return this; + } } /** @@ -275,20 +279,20 @@ class Updater extends Base { * @this Updater */ function getCheckStateCallback() { - return function (current) { - // this is a reference to the current object (therefore no array function here) - if (this instanceof HTMLInputElement) { - if (["radio", "checkbox"].indexOf(this.type) !== -1) { - return `${this.value}` === `${current}` ? "true" : undefined; - } - } else if (this instanceof HTMLOptionElement) { - if (isArray(current) && current.indexOf(this.value) !== -1) { - return "true"; - } - - return undefined; - } - }; + return function (current) { + // this is a reference to the current object (therefore no array function here) + if (this instanceof HTMLInputElement) { + if (["radio", "checkbox"].indexOf(this.type) !== -1) { + return `${this.value}` === `${current}` ? "true" : undefined; + } + } else if (this instanceof HTMLOptionElement) { + if (isArray(current) && current.indexOf(this.value) !== -1) { + return "true"; + } + + return undefined; + } + }; } /** @@ -303,41 +307,41 @@ const symbol = Symbol("@schukai/monster/updater@@EventHandler"); * @throws {Error} the bind argument must start as a value with a path */ function getControlEventHandler() { - if (this[symbol]) { - return this[symbol]; - } - - /** - * @throws {Error} the bind argument must start as a value with a path. - * @throws {Error} unsupported object - * @param {Event} event - */ - this[symbol] = (event) => { - const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND); - - if (element === undefined) { - return; - } - - if (this[timerElementEventHandlerSymbol] instanceof DeadMansSwitch) { - try { - this[timerElementEventHandlerSymbol].touch(); - return; - } catch (e) { - delete this[timerElementEventHandlerSymbol]; - } - } - - this[timerElementEventHandlerSymbol] = new DeadMansSwitch(50, () => { - try { - retrieveAndSetValue.call(this, element); - } catch (e) { - addErrorAttribute(element, e); - } - }); - }; - - return this[symbol]; + if (this[symbol]) { + return this[symbol]; + } + + /** + * @throws {Error} the bind argument must start as a value with a path. + * @throws {Error} unsupported object + * @param {Event} event + */ + this[symbol] = (event) => { + const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND); + + if (element === undefined) { + return; + } + + if (this[timerElementEventHandlerSymbol] instanceof DeadMansSwitch) { + try { + this[timerElementEventHandlerSymbol].touch(); + return; + } catch (e) { + delete this[timerElementEventHandlerSymbol]; + } + } + + this[timerElementEventHandlerSymbol] = new DeadMansSwitch(50, () => { + try { + retrieveAndSetValue.call(this, element); + } catch (e) { + addErrorAttribute(element, e); + } + }); + }; + + return this[symbol]; } /** @@ -347,186 +351,184 @@ function getControlEventHandler() { * @private */ function retrieveAndSetValue(element) { - const pathfinder = new Pathfinder(this[internalSymbol].subject.getSubject()); - - let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND); - if (path === null) - throw new Error("the bind argument must start as a value with a path"); - - if (path.indexOf("path:") !== 0) { - throw new Error("the bind argument must start as a value with a path"); - } - - path = path.substring(5); // remove path: from the string - - let value; - - if (element instanceof HTMLInputElement) { - switch (element.type) { - case "checkbox": - value = element.checked ? element.value : undefined; - break; - default: - value = element.value; - break; - } - } else if (element instanceof HTMLTextAreaElement) { - value = element.value; - } else if (element instanceof HTMLSelectElement) { - switch (element.type) { - case "select-one": - value = element.value; - break; - case "select-multiple": - value = element.value; - - let options = element?.selectedOptions; - if (options === undefined) - options = element.querySelectorAll(":scope option:checked"); - value = Array.from(options).map(({value}) => value); - - break; - } - - // values from custom elements - } else if ( - (element?.constructor?.prototype && - !!Object.getOwnPropertyDescriptor( - element.constructor.prototype, - "value", - )?.["get"]) || - "value" in element - ) { - value = element?.["value"]; - } else { - throw new Error("unsupported object"); - } - - const type = element.getAttribute(ATTRIBUTE_UPDATER_BIND_TYPE); - switch (type) { - - case "integer?": - case "int?": - case "number?": - value = Number(value); - if (isNaN(value)||0===value) { - value = undefined; - } - break; - - case "number": - case "int": - case "float": - case "integer": - value = Number(value); - if (isNaN(value)) { - value = 0; - } - break; - case "boolean": - case "bool": - case "checkbox": - value = value === "true" || value === "1" || value === "on" || value === true; - break; - - case "string[]": - if (isString(value)) { - - if (value.trim() === "") { - value = []; - } else { - value = value.split(",").map((v) => `${v}`); - } - } else if (isInteger(value)) { - value = [`${value}`]; - } else if (value === undefined || value === null) { - value = []; - } else if (isArray(value)) { - value = value.map((v) => `${v}`); - } else { - throw new Error("unsupported value"); - } - - break; - - case "int[]": - case "integer[]": - - if (isString(value)) { - - if (value.trim() === "") { - value = []; - } else { - value = value.split(",").map((v) => { - try { - return parseInt(v, 10); - } catch (e) { - } - return -1; - }).filter((v) => v !== -1); - } - - } else if (isInteger(value)) { - value = [value]; - } else if (value === undefined || value === null) { - value = []; - } else if (isArray(value)) { - value = value.split(",").map((v) => { - try { - return parseInt(v, 10); - } catch (e) { - } - return -1; - }).filter((v) => v !== -1); - } else { - throw new Error("unsupported value"); - } - - break; - case "[]": - case "array": - case "list": - - if (isString(value)) { - if (value.trim() === "") { - value = []; - } else { - value = value.split(","); - } - } else if (isInteger(value)) { - value = [value]; - } else if (value === undefined || value === null) { - value = []; - } else if (isArray(value)) { - // nothing to do - } else { - throw new Error("unsupported value for array"); - } - break; - case "object": - case "json": - - if (isString(value)) { - value = JSON.parse(value); - } else { - throw new Error("unsupported value for object"); - } - - break; - default: - break; - } - - const copy = clone(this[internalSymbol].subject.getRealSubject()); - - const pf = new Pathfinder(copy); - pf.setVia(path, value); - - const diffResult = diff(copy, this[internalSymbol].subject.getRealSubject()); - - if (diffResult.length > 0) { - pathfinder.setVia(path, value); - } + const pathfinder = new Pathfinder(this[internalSymbol].subject.getSubject()); + + let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND); + if (path === null) + throw new Error("the bind argument must start as a value with a path"); + + if (path.indexOf("path:") !== 0) { + throw new Error("the bind argument must start as a value with a path"); + } + + path = path.substring(5); // remove path: from the string + + let value; + + if (element instanceof HTMLInputElement) { + switch (element.type) { + case "checkbox": + value = element.checked ? element.value : undefined; + break; + default: + value = element.value; + break; + } + } else if (element instanceof HTMLTextAreaElement) { + value = element.value; + } else if (element instanceof HTMLSelectElement) { + switch (element.type) { + case "select-one": + value = element.value; + break; + case "select-multiple": + value = element.value; + + let options = element?.selectedOptions; + if (options === undefined) + options = element.querySelectorAll(":scope option:checked"); + value = Array.from(options).map(({ value }) => value); + + break; + } + + // values from custom elements + } else if ( + (element?.constructor?.prototype && + !!Object.getOwnPropertyDescriptor( + element.constructor.prototype, + "value", + )?.["get"]) || + "value" in element + ) { + value = element?.["value"]; + } else { + throw new Error("unsupported object"); + } + + const type = element.getAttribute(ATTRIBUTE_UPDATER_BIND_TYPE); + switch (type) { + case "integer?": + case "int?": + case "number?": + value = Number(value); + if (isNaN(value) || 0 === value) { + value = undefined; + } + break; + + case "number": + case "int": + case "float": + case "integer": + value = Number(value); + if (isNaN(value)) { + value = 0; + } + break; + case "boolean": + case "bool": + case "checkbox": + value = + value === "true" || value === "1" || value === "on" || value === true; + break; + + case "string[]": + if (isString(value)) { + if (value.trim() === "") { + value = []; + } else { + value = value.split(",").map((v) => `${v}`); + } + } else if (isInteger(value)) { + value = [`${value}`]; + } else if (value === undefined || value === null) { + value = []; + } else if (isArray(value)) { + value = value.map((v) => `${v}`); + } else { + throw new Error("unsupported value"); + } + + break; + + case "int[]": + case "integer[]": + if (isString(value)) { + if (value.trim() === "") { + value = []; + } else { + value = value + .split(",") + .map((v) => { + try { + return parseInt(v, 10); + } catch (e) {} + return -1; + }) + .filter((v) => v !== -1); + } + } else if (isInteger(value)) { + value = [value]; + } else if (value === undefined || value === null) { + value = []; + } else if (isArray(value)) { + value = value + .split(",") + .map((v) => { + try { + return parseInt(v, 10); + } catch (e) {} + return -1; + }) + .filter((v) => v !== -1); + } else { + throw new Error("unsupported value"); + } + + break; + case "[]": + case "array": + case "list": + if (isString(value)) { + if (value.trim() === "") { + value = []; + } else { + value = value.split(","); + } + } else if (isInteger(value)) { + value = [value]; + } else if (value === undefined || value === null) { + value = []; + } else if (isArray(value)) { + // nothing to do + } else { + throw new Error("unsupported value for array"); + } + break; + case "object": + case "json": + if (isString(value)) { + value = JSON.parse(value); + } else { + throw new Error("unsupported value for object"); + } + + break; + default: + break; + } + + const copy = clone(this[internalSymbol].subject.getRealSubject()); + + const pf = new Pathfinder(copy); + pf.setVia(path, value); + + const diffResult = diff(copy, this[internalSymbol].subject.getRealSubject()); + + if (diffResult.length > 0) { + pathfinder.setVia(path, value); + } } /** @@ -536,15 +538,15 @@ function retrieveAndSetValue(element) { * @private */ function retrieveFromBindings() { - if (this[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`)) { - retrieveAndSetValue.call(this, this[internalSymbol].element); - } - - for (const [, element] of this[internalSymbol].element - .querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`) - .entries()) { - retrieveAndSetValue.call(this, element); - } + if (this[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`)) { + retrieveAndSetValue.call(this, this[internalSymbol].element); + } + + for (const [, element] of this[internalSymbol].element + .querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`) + .entries()) { + retrieveAndSetValue.call(this, element); + } } /** @@ -555,11 +557,11 @@ function retrieveFromBindings() { * @return {void} */ function removeElement(change) { - for (const [, element] of this[internalSymbol].element - .querySelectorAll(`:scope [${ATTRIBUTE_UPDATER_REMOVE}]`) - .entries()) { - element.parentNode.removeChild(element); - } + for (const [, element] of this[internalSymbol].element + .querySelectorAll(`:scope [${ATTRIBUTE_UPDATER_REMOVE}]`) + .entries()) { + element.parentNode.removeChild(element); + } } /** @@ -575,128 +577,128 @@ function removeElement(change) { * @this Updater */ function insertElement(change) { - const subject = this[internalSymbol].subject.getRealSubject(); + const subject = this[internalSymbol].subject.getRealSubject(); - const mem = new WeakSet(); - let wd = 0; + const mem = new WeakSet(); + let wd = 0; - const container = this[internalSymbol].element; + const container = this[internalSymbol].element; - while (true) { - let found = false; - wd++; - - const p = clone(change?.["path"]); - if (!isArray(p)) return; - - while (p.length > 0) { - const current = p.join("."); - - let iterator = new Set(); - const query = `[${ATTRIBUTE_UPDATER_INSERT}*="path:${current}"]`; - - const e = container.querySelectorAll(query); - - if (e.length > 0) { - iterator = new Set([...e]); - } - - if (container.matches(query)) { - iterator.add(container); - } - - for (const [, containerElement] of iterator.entries()) { - if (mem.has(containerElement)) continue; - mem.add(containerElement); - - found = true; - - const attributes = containerElement.getAttribute( - ATTRIBUTE_UPDATER_INSERT, - ); - if (attributes === null) continue; - - const def = trimSpaces(attributes); - const i = def.indexOf(" "); - const key = trimSpaces(def.substr(0, i)); - const refPrefix = `${key}-`; - const cmd = trimSpaces(def.substr(i)); - - // this case is actually excluded by the query but is nevertheless checked again here - if (cmd.indexOf("|") > 0) { - throw new Error("pipes are not allowed when cloning a node."); - } - - const pipe = new Pipe(cmd); - this[internalSymbol].callbacks.forEach((f, n) => { - pipe.setCallback(n, f); - }); - - let value; - try { - containerElement.removeAttribute(ATTRIBUTE_ERRORMESSAGE); - value = pipe.run(subject); - } catch (e) { - addErrorAttribute(containerElement, e); - } - - const dataPath = cmd.split(":").pop(); - - let insertPoint; - if (containerElement.hasChildNodes()) { - insertPoint = containerElement.lastChild; - } - - if (!isIterable(value)) { - throw new Error("the value is not iterable"); - } - - const available = new Set(); - - for (const [i] of Object.entries(value)) { - const ref = refPrefix + i; - const currentPath = `${dataPath}.${i}`; - - available.add(ref); - const refElement = containerElement.querySelector( - `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`, - ); - - if (refElement instanceof HTMLElement) { - insertPoint = refElement; - continue; - } - - appendNewDocumentFragment(containerElement, key, ref, currentPath); - } - - const nodes = containerElement.querySelectorAll( - `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}*="${refPrefix}"]`, - ); - - for (const [, node] of Object.entries(nodes)) { - if ( - !available.has( - node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE), - ) - ) { - try { - containerElement.removeChild(node); - } catch (e) { - addErrorAttribute(containerElement, e); - } - } - } - } - - p.pop(); - } - - if (found === false) break; - if (wd++ > 200) { - throw new Error("the maximum depth for the recursion is reached."); - } - } + while (true) { + let found = false; + wd++; + + const p = clone(change?.["path"]); + if (!isArray(p)) return; + + while (p.length > 0) { + const current = p.join("."); + + let iterator = new Set(); + const query = `[${ATTRIBUTE_UPDATER_INSERT}*="path:${current}"]`; + + const e = container.querySelectorAll(query); + + if (e.length > 0) { + iterator = new Set([...e]); + } + + if (container.matches(query)) { + iterator.add(container); + } + + for (const [, containerElement] of iterator.entries()) { + if (mem.has(containerElement)) continue; + mem.add(containerElement); + + found = true; + + const attributes = containerElement.getAttribute( + ATTRIBUTE_UPDATER_INSERT, + ); + if (attributes === null) continue; + + const def = trimSpaces(attributes); + const i = def.indexOf(" "); + const key = trimSpaces(def.substr(0, i)); + const refPrefix = `${key}-`; + const cmd = trimSpaces(def.substr(i)); + + // this case is actually excluded by the query but is nevertheless checked again here + if (cmd.indexOf("|") > 0) { + throw new Error("pipes are not allowed when cloning a node."); + } + + const pipe = new Pipe(cmd); + this[internalSymbol].callbacks.forEach((f, n) => { + pipe.setCallback(n, f); + }); + + let value; + try { + containerElement.removeAttribute(ATTRIBUTE_ERRORMESSAGE); + value = pipe.run(subject); + } catch (e) { + addErrorAttribute(containerElement, e); + } + + const dataPath = cmd.split(":").pop(); + + let insertPoint; + if (containerElement.hasChildNodes()) { + insertPoint = containerElement.lastChild; + } + + if (!isIterable(value)) { + throw new Error("the value is not iterable"); + } + + const available = new Set(); + + for (const [i] of Object.entries(value)) { + const ref = refPrefix + i; + const currentPath = `${dataPath}.${i}`; + + available.add(ref); + const refElement = containerElement.querySelector( + `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`, + ); + + if (refElement instanceof HTMLElement) { + insertPoint = refElement; + continue; + } + + appendNewDocumentFragment(containerElement, key, ref, currentPath); + } + + const nodes = containerElement.querySelectorAll( + `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}*="${refPrefix}"]`, + ); + + for (const [, node] of Object.entries(nodes)) { + if ( + !available.has( + node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE), + ) + ) { + try { + containerElement.removeChild(node); + } catch (e) { + addErrorAttribute(containerElement, e); + } + } + } + } + + p.pop(); + } + + if (found === false) break; + if (wd++ > 200) { + throw new Error("the maximum depth for the recursion is reached."); + } + } } /** @@ -711,17 +713,17 @@ function insertElement(change) { * @throws {Error} no template was found with the specified key. */ function appendNewDocumentFragment(container, key, ref, path) { - const template = findDocumentTemplate(key, container); + const template = findDocumentTemplate(key, container); - const nodes = template.createDocumentFragment(); - for (const [, node] of Object.entries(nodes.childNodes)) { - if (node instanceof HTMLElement) { - applyRecursive(node, key, path); - node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref); - } + const nodes = template.createDocumentFragment(); + for (const [, node] of Object.entries(nodes.childNodes)) { + if (node instanceof HTMLElement) { + applyRecursive(node, key, path); + node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref); + } - container.appendChild(node); - } + container.appendChild(node); + } } /** @@ -734,32 +736,31 @@ function appendNewDocumentFragment(container, key, ref, path) { * @return {void} */ function applyRecursive(node, key, path) { - if (!(node instanceof HTMLElement)) { - return - } - - if (node.hasAttribute(ATTRIBUTE_UPDATER_REPLACE)) { - const value = node.getAttribute(ATTRIBUTE_UPDATER_REPLACE); - node.setAttribute( - ATTRIBUTE_UPDATER_REPLACE, - value.replaceAll(`path:${key}`, `path:${path}`), - ); - } - - if (node.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { - const value = node.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); - node.setAttribute( - ATTRIBUTE_UPDATER_ATTRIBUTES, - value.replaceAll(`path:${key}`, `path:${path}`), - ); - } - - for (const [, child] of Object.entries(node.childNodes)) { - if (child instanceof HTMLElement) { - applyRecursive(child, key, path); - } - } - + if (!(node instanceof HTMLElement)) { + return; + } + + if (node.hasAttribute(ATTRIBUTE_UPDATER_REPLACE)) { + const value = node.getAttribute(ATTRIBUTE_UPDATER_REPLACE); + node.setAttribute( + ATTRIBUTE_UPDATER_REPLACE, + value.replaceAll(`path:${key}`, `path:${path}`), + ); + } + + if (node.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { + const value = node.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); + node.setAttribute( + ATTRIBUTE_UPDATER_ATTRIBUTES, + value.replaceAll(`path:${key}`, `path:${path}`), + ); + } + + for (const [, child] of Object.entries(node.childNodes)) { + if (child instanceof HTMLElement) { + applyRecursive(child, key, path); + } + } } /** @@ -771,19 +772,19 @@ function applyRecursive(node, key, path) { * @this Updater */ function updateContent(change) { - const subject = this[internalSymbol].subject.getRealSubject(); - - const p = clone(change?.["path"]); - runUpdateContent.call(this, this[internalSymbol].element, p, subject); - - const slots = this[internalSymbol].element.querySelectorAll("slot"); - if (slots.length > 0) { - for (const [, slot] of Object.entries(slots)) { - for (const [, element] of Object.entries(slot.assignedNodes())) { - runUpdateContent.call(this, element, p, subject); - } - } - } + const subject = this[internalSymbol].subject.getRealSubject(); + + const p = clone(change?.["path"]); + runUpdateContent.call(this, this[internalSymbol].element, p, subject); + + const slots = this[internalSymbol].element.querySelectorAll("slot"); + if (slots.length > 0) { + for (const [, slot] of Object.entries(slots)) { + for (const [, element] of Object.entries(slot.assignedNodes())) { + runUpdateContent.call(this, element, p, subject); + } + } + } } /** @@ -796,64 +797,64 @@ function updateContent(change) { * @return {void} */ function runUpdateContent(container, parts, subject) { - if (!isArray(parts)) return; - if (!(container instanceof HTMLElement)) return; - parts = clone(parts); - - const mem = new WeakSet(); - - while (parts.length > 0) { - const current = parts.join("."); - parts.pop(); - - // Unfortunately, static data is always changed as well, since it is not possible to react to changes here. - const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`; - const e = container.querySelectorAll(`${query}`); - - const iterator = new Set([...e]); - - if (container.matches(query)) { - iterator.add(container); - } - - /** - * @type {HTMLElement} - */ - for (const [element] of iterator.entries()) { - if (mem.has(element)) return; - mem.add(element); - - const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE); - const cmd = trimSpaces(attributes); - - const pipe = new Pipe(cmd); - this[internalSymbol].callbacks.forEach((f, n) => { - pipe.setCallback(n, f); - }); - - let value; - try { - element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); - value = pipe.run(subject); - } catch (e) { - element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); - } - - if (value instanceof HTMLElement) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - - try { - element.appendChild(value); - } catch (e) { - addErrorAttribute(element, e); - } - } else { - element.innerHTML = value; - } - } - } + if (!isArray(parts)) return; + if (!(container instanceof HTMLElement)) return; + parts = clone(parts); + + const mem = new WeakSet(); + + while (parts.length > 0) { + const current = parts.join("."); + parts.pop(); + + // Unfortunately, static data is always changed as well, since it is not possible to react to changes here. + const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`; + const e = container.querySelectorAll(`${query}`); + + const iterator = new Set([...e]); + + if (container.matches(query)) { + iterator.add(container); + } + + /** + * @type {HTMLElement} + */ + for (const [element] of iterator.entries()) { + if (mem.has(element)) return; + mem.add(element); + + const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE); + const cmd = trimSpaces(attributes); + + const pipe = new Pipe(cmd); + this[internalSymbol].callbacks.forEach((f, n) => { + pipe.setCallback(n, f); + }); + + let value; + try { + element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); + value = pipe.run(subject); + } catch (e) { + element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); + } + + if (value instanceof HTMLElement) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + + try { + element.appendChild(value); + } catch (e) { + addErrorAttribute(element, e); + } + } else { + element.innerHTML = value; + } + } + } } /** @@ -863,9 +864,9 @@ function runUpdateContent(container, parts, subject) { * @return {void} */ function updateAttributes(change) { - const subject = this[internalSymbol].subject.getRealSubject(); - const p = clone(change?.["path"]); - runUpdateAttributes.call(this, this[internalSymbol].element, p, subject); + const subject = this[internalSymbol].subject.getRealSubject(); + const p = clone(change?.["path"]); + runUpdateAttributes.call(this, this[internalSymbol].element, p, subject); } /** @@ -877,70 +878,70 @@ function updateAttributes(change) { * @this Updater */ function runUpdateAttributes(container, parts, subject) { - if (!isArray(parts)) return; - parts = clone(parts); + if (!isArray(parts)) return; + parts = clone(parts); - const mem = new WeakSet(); + const mem = new WeakSet(); - while (parts.length > 0) { - const current = parts.join("."); - parts.pop(); + while (parts.length > 0) { + const current = parts.join("."); + parts.pop(); - let iterator = new Set(); + let iterator = new Set(); - const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`; + const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`; - const e = container.querySelectorAll(query); + const e = container.querySelectorAll(query); - if (e.length > 0) { - iterator = new Set([...e]); - } + if (e.length > 0) { + iterator = new Set([...e]); + } - if (container.matches(query)) { - iterator.add(container); - } + if (container.matches(query)) { + iterator.add(container); + } - for (const [element] of iterator.entries()) { - if (mem.has(element)) return; - mem.add(element); + for (const [element] of iterator.entries()) { + if (mem.has(element)) return; + mem.add(element); - // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set - if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { - continue; - } + // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set + if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { + continue; + } - const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); + const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); - for (let [, def] of Object.entries(attributes.split(","))) { - def = trimSpaces(def); - const i = def.indexOf(" "); - const name = trimSpaces(def.substr(0, i)); - const cmd = trimSpaces(def.substr(i)); + for (let [, def] of Object.entries(attributes.split(","))) { + def = trimSpaces(def); + const i = def.indexOf(" "); + const name = trimSpaces(def.substr(0, i)); + const cmd = trimSpaces(def.substr(i)); - const pipe = new Pipe(cmd); + const pipe = new Pipe(cmd); - this[internalSymbol].callbacks.forEach((f, n) => { - pipe.setCallback(n, f, element); - }); + this[internalSymbol].callbacks.forEach((f, n) => { + pipe.setCallback(n, f, element); + }); - let value; - try { - element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); - value = pipe.run(subject); - } catch (e) { - addErrorAttribute(element, e); - } + let value; + try { + element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); + value = pipe.run(subject); + } catch (e) { + addErrorAttribute(element, e); + } - if (value === undefined) { - element.removeAttribute(name); - } else if (element.getAttribute(name) !== value) { - element.setAttribute(name, value); - } + if (value === undefined) { + element.removeAttribute(name); + } else if (element.getAttribute(name) !== value) { + element.setAttribute(name, value); + } - handleInputControlAttributeUpdate.call(this, element, name, value); - } - } - } + handleInputControlAttributeUpdate.call(this, element, name, value); + } + } + } } /** @@ -953,52 +954,52 @@ function runUpdateAttributes(container, parts, subject) { */ function handleInputControlAttributeUpdate(element, name, value) { - if (element instanceof HTMLSelectElement) { - switch (element.type) { - case "select-multiple": - for (const [index, opt] of Object.entries(element.options)) { - opt.selected = value.indexOf(opt.value) !== -1; - } - - break; - case "select-one": - // Only one value may be selected - - for (const [index, opt] of Object.entries(element.options)) { - if (opt.value === value) { - element.selectedIndex = index; - break; - } - } - - break; - } - } else if (element instanceof HTMLInputElement) { - switch (element.type) { - case "radio": - if (name === "checked") { - element.checked = value !== undefined; - } - break; - - case "checkbox": - if (name === "checked") { - element.checked = value !== undefined; - } - break; - - case "text": - default: - if (name === "value") { - element.value = value === undefined ? "" : value; - } - break; - } - } else if (element instanceof HTMLTextAreaElement) { - if (name === "value") { - element.value = value === undefined ? "" : value; - } - } + if (element instanceof HTMLSelectElement) { + switch (element.type) { + case "select-multiple": + for (const [index, opt] of Object.entries(element.options)) { + opt.selected = value.indexOf(opt.value) !== -1; + } + + break; + case "select-one": + // Only one value may be selected + + for (const [index, opt] of Object.entries(element.options)) { + if (opt.value === value) { + element.selectedIndex = index; + break; + } + } + + break; + } + } else if (element instanceof HTMLInputElement) { + switch (element.type) { + case "radio": + if (name === "checked") { + element.checked = value !== undefined; + } + break; + + case "checkbox": + if (name === "checked") { + element.checked = value !== undefined; + } + break; + + case "text": + default: + if (name === "value") { + element.value = value === undefined ? "" : value; + } + break; + } + } else if (element instanceof HTMLTextAreaElement) { + if (name === "value") { + element.value = value === undefined ? "" : value; + } + } } /** @@ -1017,78 +1018,81 @@ function handleInputControlAttributeUpdate(element, name, value) { * @throws {TypeError} symbol must be an instance of Symbol */ function addObjectWithUpdaterToElement(elements, symbol, object, config = {}) { - if (!(this instanceof HTMLElement)) { - throw new TypeError( - "the context of this function must be an instance of HTMLElement", - ); - } - - if (!(typeof symbol === "symbol")) { - throw new TypeError("symbol must be an instance of Symbol"); - } - - const updaters = new Set(); - - if (elements instanceof NodeList) { - elements = new Set([...elements]); - } else if (elements instanceof HTMLElement) { - elements = new Set([elements]); - } else if (elements instanceof Set) { - } else { - throw new TypeError( - `elements is not a valid type. (actual: ${typeof elements})`, - ); - } - - const result = []; - - const updaterCallbacks = []; - const cb = this?.[updaterTransformerMethodsSymbol]; - if (this instanceof HTMLElement && typeof cb === "function") { - const callbacks = cb.call(this); - if (typeof callbacks === "object") { - for (const [name, callback] of Object.entries(callbacks)) { - if (typeof callback === "function") { - updaterCallbacks.push([name, callback]); - } else { - addErrorAttribute(this, `onUpdaterPipeCallbacks: ${name} is not a function`); - } - } - } else { - addErrorAttribute( - this, - `onUpdaterPipeCallbacks do not return an object with functions` - ); - } - } - - elements.forEach((element) => { - if (!(element instanceof HTMLElement)) return; - if (element instanceof HTMLTemplateElement) return; - - const u = new Updater(element, object); - updaters.add(u); - - if (updaterCallbacks.length > 0) { - for (const [name, callback] of updaterCallbacks) { - u.setCallback(name, callback); - } - } - - result.push( - u.run().then(() => { - if (config.eventProcessing === true) { - u.enableEventProcessing(); - } - - return u; - }), - ); - }); - - if (updaters.size > 0) { - addToObjectLink(this, symbol, updaters); - } - - return result; + if (!(this instanceof HTMLElement)) { + throw new TypeError( + "the context of this function must be an instance of HTMLElement", + ); + } + + if (!(typeof symbol === "symbol")) { + throw new TypeError("symbol must be an instance of Symbol"); + } + + const updaters = new Set(); + + if (elements instanceof NodeList) { + elements = new Set([...elements]); + } else if (elements instanceof HTMLElement) { + elements = new Set([elements]); + } else if (elements instanceof Set) { + } else { + throw new TypeError( + `elements is not a valid type. (actual: ${typeof elements})`, + ); + } + + const result = []; + + const updaterCallbacks = []; + const cb = this?.[updaterTransformerMethodsSymbol]; + if (this instanceof HTMLElement && typeof cb === "function") { + const callbacks = cb.call(this); + if (typeof callbacks === "object") { + for (const [name, callback] of Object.entries(callbacks)) { + if (typeof callback === "function") { + updaterCallbacks.push([name, callback]); + } else { + addErrorAttribute( + this, + `onUpdaterPipeCallbacks: ${name} is not a function`, + ); + } + } + } else { + addErrorAttribute( + this, + `onUpdaterPipeCallbacks do not return an object with functions`, + ); + } + } + + elements.forEach((element) => { + if (!(element instanceof HTMLElement)) return; + if (element instanceof HTMLTemplateElement) return; + + const u = new Updater(element, object); + updaters.add(u); + + if (updaterCallbacks.length > 0) { + for (const [name, callback] of updaterCallbacks) { + u.setCallback(name, callback); + } + } + + result.push( + u.run().then(() => { + if (config.eventProcessing === true) { + u.enableEventProcessing(); + } + + return u; + }), + ); + }); + + if (updaters.size > 0) { + addToObjectLink(this, symbol, updaters); + } + + return result; }