From 19fed8d8d84b6f17b6f75c25c1ac98893dc07936 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Wed, 26 Jun 2024 19:58:18 +0200 Subject: [PATCH] chore: tidy and stylings --- development/issues/closed/192.html | 4 +- development/scripts/buildStylesheets.mjs | 5 - source/components/datatable/dataset.mjs | 544 ++++++------ .../components/datatable/datasource/dom.mjs | 4 +- .../components/datatable/datasource/rest.mjs | 818 +++++++++--------- source/components/datatable/filter.mjs | 1 - source/components/form/field-set.mjs | 438 +++++----- source/components/form/form.mjs | 324 +++---- source/components/form/reload.mjs | 422 ++++----- source/components/form/select.mjs | 25 +- source/data/datasource/server.mjs | 212 ++--- source/data/datasource/server/restapi.mjs | 384 ++++---- 12 files changed, 1563 insertions(+), 1618 deletions(-) diff --git a/development/issues/closed/192.html b/development/issues/closed/192.html index 9e63deee2..faa0e8d8b 100644 --- a/development/issues/closed/192.html +++ b/development/issues/closed/192.html @@ -16,8 +16,8 @@ <main> <div id="error-message" data-monster-error="this is the error">This control has an error</div> - <monster-context-error>error1</monster-context-error> - <monster-context-error></monster-context-error> + <monster-context-error>error1</monster-context-error> das ist ein test + <monster-context-error></monster-context-error> dieser hat keine fehlermeldung und damit keinen fehler. </main> </body> diff --git a/development/scripts/buildStylesheets.mjs b/development/scripts/buildStylesheets.mjs index 99cd7b750..2e20a5507 100644 --- a/development/scripts/buildStylesheets.mjs +++ b/development/scripts/buildStylesheets.mjs @@ -15,11 +15,6 @@ if (!filename.endsWith(".pcss")) { process.exit(1); } -if (existsSync(`${projectRoot}/dist`) === false) { - console.error("Creating dist directory"); - process.exit(1); -} - buildCSS(filename, createScriptFilenameFromStyleFilename(filename)).then(() => { }).catch((e) => { console.error(e.message); diff --git a/source/components/datatable/dataset.mjs b/source/components/datatable/dataset.mjs index 8ba57d289..142239a47 100644 --- a/source/components/datatable/dataset.mjs +++ b/source/components/datatable/dataset.mjs @@ -12,33 +12,33 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {instanceSymbol, internalSymbol} from "../../constants.mjs"; -import {Pathfinder} from "../../data/pathfinder.mjs"; -import {getLinkedObjects, hasObjectLink} from "../../dom/attributes.mjs"; -import {customElementUpdaterLinkSymbol} from "../../dom/constants.mjs"; +import { instanceSymbol, internalSymbol } from "../../constants.mjs"; +import { Pathfinder } from "../../data/pathfinder.mjs"; +import { getLinkedObjects, hasObjectLink } from "../../dom/attributes.mjs"; +import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs"; import { - assembleMethodSymbol, - CustomElement, - attributeObserverSymbol, - registerCustomElement, + assembleMethodSymbol, + CustomElement, + attributeObserverSymbol, + registerCustomElement, } from "../../dom/customelement.mjs"; -import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; -import {isString} from "../../types/is.mjs"; -import {Observer} from "../../types/observer.mjs"; -import {clone} from "../../util/clone.mjs"; +import { findElementWithSelectorUpwards } from "../../dom/util.mjs"; +import { isString } from "../../types/is.mjs"; +import { Observer } from "../../types/observer.mjs"; +import { clone } from "../../util/clone.mjs"; import { - ATTRIBUTE_DATASOURCE_SELECTOR, - ATTRIBUTE_DATATABLE_INDEX, + ATTRIBUTE_DATASOURCE_SELECTOR, + ATTRIBUTE_DATATABLE_INDEX, } from "./constants.mjs"; -import {Datasource} from "./datasource.mjs"; -import {DatasetStyleSheet} from "./stylesheet/dataset.mjs"; +import { Datasource } from "./datasource.mjs"; +import { DatasetStyleSheet } from "./stylesheet/dataset.mjs"; import { - handleDataSourceChanges, - datasourceLinkedElementSymbol, + handleDataSourceChanges, + datasourceLinkedElementSymbol, } from "./util.mjs"; -import {FormStyleSheet} from "../stylesheet/form.mjs"; +import { FormStyleSheet } from "../stylesheet/form.mjs"; -export {DataSet}; +export { DataSet }; /** * The data set component is used to show the data of a data source. @@ -79,219 +79,221 @@ export {DataSet}; * @summary A data set */ class DataSet extends CustomElement { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/dataset@@instance"); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - const attributes = super.observedAttributes; - attributes.push(ATTRIBUTE_DATATABLE_INDEX); - return attributes; - } - - /** - * To set the options via the html tag the attribute `data-monster-options` must be used. - * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} - * - * The individual configuration values can be found in the table. - * - * @property {Object} templates Template definitions - * @property {string} templates.main Main template - * @property {object} datasource The datasource - * @property {string} datasource.selector The selector of the datasource - * @property {object} mapping The mapping - * @property {string} mapping.data The data - * @property {number} mapping.index The index - * @property {Array} data The data - */ - get defaults() { - const obj = Object.assign({}, super.defaults, { - templates: { - main: getTemplate(), - }, - - datasource: { - selector: null, - }, - - mapping: { - data: "dataset", - index: 0, - }, - - features: { - /** - * @since 3.70.0 - * @type {boolean} - */ - refreshOnMutation: true, - }, - - /** - * @since 3.70.0 - * @type {boolean} - */ - refreshOnMutation: { - selector: "input, select, textarea" - }, - - data: {}, - }); - - updateOptionsFromArguments.call(this, obj); - return obj; - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-dataset"; - } - - /** - * This method is called when the component is created. - * @since 3.70.0 - * @returns {DataSet} - */ - refresh() { - // makes sure that handleDataSourceChanges is called - this.setOption("data", {}); - return this; - } - - /** - * - * @returns {Promise<unknown>} - */ - write() { - return new Promise((resolve, reject) => { - if (!this[datasourceLinkedElementSymbol]) { - reject(new Error("No datasource")); - return; - } - - const internalUpdateCloneData = this.getInternalUpdateCloneData(); - if (!internalUpdateCloneData) { - reject(new Error("No update data")); - return; - } - - const internalData = internalUpdateCloneData?.["data"]; - if ( - internalData === undefined || - internalData === null || - internalData === "" - ) { - reject(new Error("No data")); - return; - } - - setTimeout(() => { - const path = this.getOption("mapping.data"); - const index = this.getOption("mapping.index"); - - let pathWithIndex; - - if (isString(path) && path !== "") { - pathWithIndex = path + "." + index; - } else { - pathWithIndex = String(index); - } - - const data = this[datasourceLinkedElementSymbol].data; - const unref = JSON.stringify(data); - const ref = JSON.parse(unref); - - new Pathfinder(ref).setVia(pathWithIndex, internalData); - - this[datasourceLinkedElementSymbol].data = ref; - - resolve(); - }, 0); - }); - } - - /** - * This method is responsible for assembling the component. - * - * It calls the parent's assemble method first, then initializes control references and event handlers. - * If the `datasource.selector` option is provided and is a string, it searches for the corresponding - * element in the DOM using that selector. - * - * If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class. - * - * If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component - * attaches an observer to the datasource's changes. - * - * The observer is a function that calls the `handleDataSourceChanges` method in the context of the component. - * Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges` - * method in the component's context. - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - - initEventHandler.call(this); - - const selector = this.getOption("datasource.selector"); - - if (isString(selector)) { - const element = findElementWithSelectorUpwards(this, selector); - if (element === null) { - throw new Error("the selector must match exactly one element"); - } - - if (!(element instanceof Datasource)) { - throw new TypeError("the element must be a datasource"); - } - - this[datasourceLinkedElementSymbol] = element; - element.datasource.attachObserver( - new Observer(handleDataSourceChanges.bind(this)), - ); - } - - this.attachObserver( - new Observer(() => { - handleDataSourceChanges.call(this); - }), - ); - - if (this.getOption("features.refreshOnMutation")&&this.getOption("refreshOnMutation.selector")) { - initMutationObserver.call(this); - } - - } - - /** - * @return [CSSStyleSheet] - */ - static getCSSStyleSheet() { - return [FormStyleSheet, DatasetStyleSheet]; - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/dataset@@instance"); + } + + /** + * This method determines which attributes are to be monitored by `attributeChangedCallback()`. + * + * @return {string[]} + * @since 1.15.0 + */ + static get observedAttributes() { + const attributes = super.observedAttributes; + attributes.push(ATTRIBUTE_DATATABLE_INDEX); + return attributes; + } + + /** + * To set the options via the html tag the attribute `data-monster-options` must be used. + * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {object} datasource The datasource + * @property {string} datasource.selector The selector of the datasource + * @property {object} mapping The mapping + * @property {string} mapping.data The data + * @property {number} mapping.index The index + * @property {Array} data The data + */ + get defaults() { + const obj = Object.assign({}, super.defaults, { + templates: { + main: getTemplate(), + }, + + datasource: { + selector: null, + }, + + mapping: { + data: "dataset", + index: 0, + }, + + features: { + /** + * @since 3.70.0 + * @type {boolean} + */ + refreshOnMutation: true, + }, + + /** + * @since 3.70.0 + * @type {boolean} + */ + refreshOnMutation: { + selector: "input, select, textarea", + }, + + data: {}, + }); + + updateOptionsFromArguments.call(this, obj); + return obj; + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-dataset"; + } + + /** + * This method is called when the component is created. + * @since 3.70.0 + * @returns {DataSet} + */ + refresh() { + // makes sure that handleDataSourceChanges is called + this.setOption("data", {}); + return this; + } + + /** + * + * @returns {Promise<unknown>} + */ + write() { + return new Promise((resolve, reject) => { + if (!this[datasourceLinkedElementSymbol]) { + reject(new Error("No datasource")); + return; + } + + const internalUpdateCloneData = this.getInternalUpdateCloneData(); + if (!internalUpdateCloneData) { + reject(new Error("No update data")); + return; + } + + const internalData = internalUpdateCloneData?.["data"]; + if ( + internalData === undefined || + internalData === null || + internalData === "" + ) { + reject(new Error("No data")); + return; + } + + setTimeout(() => { + const path = this.getOption("mapping.data"); + const index = this.getOption("mapping.index"); + + let pathWithIndex; + + if (isString(path) && path !== "") { + pathWithIndex = path + "." + index; + } else { + pathWithIndex = String(index); + } + + const data = this[datasourceLinkedElementSymbol].data; + const unref = JSON.stringify(data); + const ref = JSON.parse(unref); + + new Pathfinder(ref).setVia(pathWithIndex, internalData); + + this[datasourceLinkedElementSymbol].data = ref; + + resolve(); + }, 0); + }); + } + + /** + * This method is responsible for assembling the component. + * + * It calls the parent's assemble method first, then initializes control references and event handlers. + * If the `datasource.selector` option is provided and is a string, it searches for the corresponding + * element in the DOM using that selector. + * + * If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class. + * + * If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component + * attaches an observer to the datasource's changes. + * + * The observer is a function that calls the `handleDataSourceChanges` method in the context of the component. + * Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges` + * method in the component's context. + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + + initEventHandler.call(this); + + const selector = this.getOption("datasource.selector"); + + if (isString(selector)) { + const element = findElementWithSelectorUpwards(this, selector); + if (element === null) { + throw new Error("the selector must match exactly one element"); + } + + if (!(element instanceof Datasource)) { + throw new TypeError("the element must be a datasource"); + } + + this[datasourceLinkedElementSymbol] = element; + element.datasource.attachObserver( + new Observer(handleDataSourceChanges.bind(this)), + ); + } + + this.attachObserver( + new Observer(() => { + handleDataSourceChanges.call(this); + }), + ); + + if ( + this.getOption("features.refreshOnMutation") && + this.getOption("refreshOnMutation.selector") + ) { + initMutationObserver.call(this); + } + } + + /** + * @return [CSSStyleSheet] + */ + static getCSSStyleSheet() { + return [FormStyleSheet, DatasetStyleSheet]; + } } /** * @private */ function initEventHandler() { - this[attributeObserverSymbol][ATTRIBUTE_DATATABLE_INDEX] = () => { - const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX); - if (index) { - this.setOption("mapping.index", parseInt(index, 10)); - } - }; + this[attributeObserverSymbol][ATTRIBUTE_DATATABLE_INDEX] = () => { + const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX); + if (index) { + this.setOption("mapping.index", parseInt(index, 10)); + } + }; } /** @@ -299,67 +301,65 @@ function initEventHandler() { * @param {Object} options */ function updateOptionsFromArguments(options) { - const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX); + const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX); - if (index !== null && index !== undefined) { - options.mapping.index = parseInt(index, 10); - } + if (index !== null && index !== undefined) { + options.mapping.index = parseInt(index, 10); + } - const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR); + const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR); - if (selector) { - options.datasource.selector = selector; - } + if (selector) { + options.datasource.selector = selector; + } } /** * @private */ function initMutationObserver() { - - const config = {attributes: false, childList: true, subtree: true}; - - const callback = (mutationList, observer) => { - - if (mutationList.length === 0) { - return; - } - - let doneFlag = false; - for (const mutation of mutationList) { - - if (mutation.type === "childList") { - for (const node of mutation.addedNodes) { - if(node instanceof HTMLElement && node.matches(this.getOption("refreshOnMutation.selector"))) { - doneFlag = true; - break; - } - } - - if (doneFlag) { - break; - } - } - } - - if (doneFlag) { - this.refresh(); - } - }; - - const observer = new MutationObserver(callback); - observer.observe(this, config); - + const config = { attributes: false, childList: true, subtree: true }; + + const callback = (mutationList, observer) => { + if (mutationList.length === 0) { + return; + } + + let doneFlag = false; + for (const mutation of mutationList) { + if (mutation.type === "childList") { + for (const node of mutation.addedNodes) { + if ( + node instanceof HTMLElement && + node.matches(this.getOption("refreshOnMutation.selector")) + ) { + doneFlag = true; + break; + } + } + + if (doneFlag) { + break; + } + } + } + + if (doneFlag) { + this.refresh(); + } + }; + + const observer = new MutationObserver(callback); + observer.observe(this, config); } - /** * @private * @return {string} */ function getTemplate() { - // language=HTML - return ` + // language=HTML + return ` <div data-monster-role="control" part="control"> <slot></slot> </div> diff --git a/source/components/datatable/datasource/dom.mjs b/source/components/datatable/datasource/dom.mjs index c713294db..ed6e00890 100644 --- a/source/components/datatable/datasource/dom.mjs +++ b/source/components/datatable/datasource/dom.mjs @@ -12,7 +12,7 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {Datasource, dataSourceSymbol} from "../datasource.mjs"; +import { Datasource, dataSourceSymbol } from "../datasource.mjs"; import { DatasourceStyleSheet } from "../stylesheet/datasource.mjs"; import { instanceSymbol } from "../../../constants.mjs"; import { @@ -180,7 +180,7 @@ function updateDataSource() { data = []; } - this.data=data; + this.data = data; } /** diff --git a/source/components/datatable/datasource/rest.mjs b/source/components/datatable/datasource/rest.mjs index e24ee30c6..9ee1a49b7 100644 --- a/source/components/datatable/datasource/rest.mjs +++ b/source/components/datatable/datasource/rest.mjs @@ -12,27 +12,27 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {diff} from "../../../data/diff.mjs"; -import {addAttributeToken} from "../../../dom/attributes.mjs"; -import {ATTRIBUTE_ERRORMESSAGE} from "../../../dom/constants.mjs"; -import {isArray} from "../../../types/is.mjs"; -import {Datasource, dataSourceSymbol} from "../datasource.mjs"; -import {DatasourceStyleSheet} from "../stylesheet/datasource.mjs"; -import {instanceSymbol} from "../../../constants.mjs"; +import { diff } from "../../../data/diff.mjs"; +import { addAttributeToken } from "../../../dom/attributes.mjs"; +import { ATTRIBUTE_ERRORMESSAGE } from "../../../dom/constants.mjs"; +import { isArray } from "../../../types/is.mjs"; +import { Datasource, dataSourceSymbol } from "../datasource.mjs"; +import { DatasourceStyleSheet } from "../stylesheet/datasource.mjs"; +import { instanceSymbol } from "../../../constants.mjs"; import { - assembleMethodSymbol, - registerCustomElement, + assembleMethodSymbol, + registerCustomElement, } from "../../../dom/customelement.mjs"; -import {RestAPI} from "../../../data/datasource/server/restapi.mjs"; -import {Formatter} from "../../../text/formatter.mjs"; -import {clone} from "../../../util/clone.mjs"; -import {validateBoolean} from "../../../types/validate.mjs"; -import {findElementWithIdUpwards} from "../../../dom/util.mjs"; -import {Observer} from "../../../types/observer.mjs"; -import {Pathfinder} from "../../../data/pathfinder.mjs"; -import {fireCustomEvent} from "../../../dom/events.mjs"; +import { RestAPI } from "../../../data/datasource/server/restapi.mjs"; +import { Formatter } from "../../../text/formatter.mjs"; +import { clone } from "../../../util/clone.mjs"; +import { validateBoolean } from "../../../types/validate.mjs"; +import { findElementWithIdUpwards } from "../../../dom/util.mjs"; +import { Observer } from "../../../types/observer.mjs"; +import { Pathfinder } from "../../../data/pathfinder.mjs"; +import { fireCustomEvent } from "../../../dom/events.mjs"; -export {Rest}; +export { Rest }; /** * @private @@ -46,7 +46,7 @@ const intersectionObserverHandlerSymbol = Symbol("intersectionObserverHandler"); * @type {symbol} */ const rawDataSymbol = Symbol.for( - "@schukai/monster/data/datasource/server/restapi/rawdata", + "@schukai/monster/data/datasource/server/restapi/rawdata", ); /** @@ -54,7 +54,7 @@ const rawDataSymbol = Symbol.for( * @type {symbol} */ const intersectionObserverObserverSymbol = Symbol( - "intersectionObserverObserver", + "intersectionObserverObserver", ); /** @@ -83,23 +83,23 @@ const filterObserverSymbol = Symbol("filterObserver"); * @summary A rest api datasource */ class Rest extends Datasource { - /** - * the constructor of the class - */ - constructor() { - super(); - this[dataSourceSymbol] = new RestAPI(); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/datasource/rest@@instance"); - } - - /** + /** + * the constructor of the class + */ + constructor() { + super(); + this[dataSourceSymbol] = new RestAPI(); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/datasource/rest@@instance"); + } + + /** * To set the options via the html tag the attribute `data-monster-options` must be used. * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} * @@ -130,409 +130,407 @@ class Rest extends Datasource { * @property {Object} write Write configuration */ - get defaults() { - const restOptions = new RestAPI().defaults; - - restOptions.read.parameters = { - filter: undefined, - oderBy: undefined, - page: "1", - }; - - return Object.assign({}, super.defaults, restOptions, { - templates: { - main: getTemplate(), - }, - - features: { - autoInit: false, - filter: false, - }, - - autoInit: { - intersectionObserver: false, - oneTime: true, - }, - - filter: { - id: undefined, - }, - - datatable: { - id: undefined, - }, - - response: { - path: { - message: "sys.message", - code: "sys.code", - }, - }, - }); - } - - /** - * - * @param {string} page - * @param {string} query - * @param {string} orderBy - * @returns {Monster.Components.Datatable.Datasource.Rest} - */ - setParameters({page, query, orderBy}) { - const parameters = this.getOption("read.parameters"); - if (query !== undefined) { - parameters.query = `${query}`; - parameters.page = "1"; - } - - // after a query the page is set to 1, so if the page is not set, it is set to 1 - if (page !== undefined) parameters.page = `${page}`; - if (orderBy !== undefined) parameters.order = `${orderBy}`; - this.setOption("read.parameters", parameters); - return this; - } - - /** - * @return {void} - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - - initEventHandler.call(this); - initAutoInit.call(this); - } - - /** - * @deprecated 2023-06-25 - * @returns {Promise<never>|*} - */ - reload() { - return this.fetch(); - } - - /** - * Fetches the data from the rest api - * @returns {Promise<never>|*} - */ - fetch() { - const opt = clone(this.getOption("read")); - this[dataSourceSymbol].setOption("read", opt); - - let url = this.getOption("read.url"); - const formatter = new Formatter(this.getOption("read.parameters")); - - if (!url) { - return Promise.reject(new Error("No url defined")); - } - - url = formatter.format(url); - - this[dataSourceSymbol].setOption("read.url", url); - - return new Promise((resolve, reject) => { - fireCustomEvent(this, "monster-datasource-fetch", { - datasource: this, - }); - - setTimeout(() => { - this[dataSourceSymbol] - .read() - .then((response) => { - fireCustomEvent(this, "monster-datasource-fetched", { - datasource: this, - }); - - resolve(response); - }) - .catch((error) => { - fireCustomEvent(this, "monster-datasource-error", { - error: error, - }); - - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString()); - reject(error); - }); - }, 0); - }); - } - - /** - * - * @return {CSSStyleSheet[]} - */ - static getCSSStyleSheet() { - return [DatasourceStyleSheet]; - } - - /** - * @private - * @return {string} - */ - static getTag() { - return "monster-datasource-rest"; - } - - /** - * This method activates the intersection observer manually. - * For this purpose, the option `autoInit.intersectionObserver` must be set to `false`. - * - * @returns {Monster.Components.Datatable.Datasource.Rest} - */ - initIntersectionObserver() { - initIntersectionObserver.call(this); - return this; - } - - /** - * @private - */ - connectedCallback() { - super.connectedCallback(); - - setTimeout(() => { - if (this.getOption("features.filter", false) === true) { - initFilter.call(this); - } - }, 0); - } - - /** - * @private - */ - disconnectedCallback() { - super.disconnectedCallback(); - removeFilter.call(this); - } - - /** - * @returns {Promise<never>|*} - */ - read() { - return this.fetch(); - } - - /** - * Fetches the data from the rest api - * @returns {Promise<never>|*} - */ - write() { - const opt = clone(this.getOption("write")); - this[dataSourceSymbol].setOption("write", opt); - - let url = this.getOption("write.url"); - const formatter = new Formatter(this.getOption("write.parameters")); - - if (!url) { - return Promise.reject(new Error("No url defined")); - } - - url = formatter.format(url); - - this[dataSourceSymbol].setOption("write.url", url); - - return new Promise((resolve, reject) => { - fireCustomEvent(this, "monster-datasource-fetch", { - datasource: this, - }); - - setTimeout(() => { - this[dataSourceSymbol] - .write() - .then((response) => { - fireCustomEvent(this, "monster-datasource-fetched", { - datasource: this, - }); - - resolve(response); - }) - .catch((error) => { - fireCustomEvent(this, "monster-datasource-error", { - error: error, - }); - - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString()); - reject(error); - }); - }, 0); - }); - } + get defaults() { + const restOptions = new RestAPI().defaults; + + restOptions.read.parameters = { + filter: undefined, + oderBy: undefined, + page: "1", + }; + + return Object.assign({}, super.defaults, restOptions, { + templates: { + main: getTemplate(), + }, + + features: { + autoInit: false, + filter: false, + }, + + autoInit: { + intersectionObserver: false, + oneTime: true, + }, + + filter: { + id: undefined, + }, + + datatable: { + id: undefined, + }, + + response: { + path: { + message: "sys.message", + code: "sys.code", + }, + }, + }); + } + + /** + * + * @param {string} page + * @param {string} query + * @param {string} orderBy + * @returns {Monster.Components.Datatable.Datasource.Rest} + */ + setParameters({ page, query, orderBy }) { + const parameters = this.getOption("read.parameters"); + if (query !== undefined) { + parameters.query = `${query}`; + parameters.page = "1"; + } + + // after a query the page is set to 1, so if the page is not set, it is set to 1 + if (page !== undefined) parameters.page = `${page}`; + if (orderBy !== undefined) parameters.order = `${orderBy}`; + this.setOption("read.parameters", parameters); + return this; + } + + /** + * @return {void} + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + + initEventHandler.call(this); + initAutoInit.call(this); + } + + /** + * @deprecated 2023-06-25 + * @returns {Promise<never>|*} + */ + reload() { + return this.fetch(); + } + + /** + * Fetches the data from the rest api + * @returns {Promise<never>|*} + */ + fetch() { + const opt = clone(this.getOption("read")); + this[dataSourceSymbol].setOption("read", opt); + + let url = this.getOption("read.url"); + const formatter = new Formatter(this.getOption("read.parameters")); + + if (!url) { + return Promise.reject(new Error("No url defined")); + } + + url = formatter.format(url); + + this[dataSourceSymbol].setOption("read.url", url); + + return new Promise((resolve, reject) => { + fireCustomEvent(this, "monster-datasource-fetch", { + datasource: this, + }); + + setTimeout(() => { + this[dataSourceSymbol] + .read() + .then((response) => { + fireCustomEvent(this, "monster-datasource-fetched", { + datasource: this, + }); + + resolve(response); + }) + .catch((error) => { + fireCustomEvent(this, "monster-datasource-error", { + error: error, + }); + + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString()); + reject(error); + }); + }, 0); + }); + } + + /** + * + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [DatasourceStyleSheet]; + } + + /** + * @private + * @return {string} + */ + static getTag() { + return "monster-datasource-rest"; + } + + /** + * This method activates the intersection observer manually. + * For this purpose, the option `autoInit.intersectionObserver` must be set to `false`. + * + * @returns {Monster.Components.Datatable.Datasource.Rest} + */ + initIntersectionObserver() { + initIntersectionObserver.call(this); + return this; + } + + /** + * @private + */ + connectedCallback() { + super.connectedCallback(); + + setTimeout(() => { + if (this.getOption("features.filter", false) === true) { + initFilter.call(this); + } + }, 0); + } + + /** + * @private + */ + disconnectedCallback() { + super.disconnectedCallback(); + removeFilter.call(this); + } + + /** + * @returns {Promise<never>|*} + */ + read() { + return this.fetch(); + } + + /** + * Fetches the data from the rest api + * @returns {Promise<never>|*} + */ + write() { + const opt = clone(this.getOption("write")); + this[dataSourceSymbol].setOption("write", opt); + + let url = this.getOption("write.url"); + const formatter = new Formatter(this.getOption("write.parameters")); + + if (!url) { + return Promise.reject(new Error("No url defined")); + } + + url = formatter.format(url); + + this[dataSourceSymbol].setOption("write.url", url); + + return new Promise((resolve, reject) => { + fireCustomEvent(this, "monster-datasource-fetch", { + datasource: this, + }); + + setTimeout(() => { + this[dataSourceSymbol] + .write() + .then((response) => { + fireCustomEvent(this, "monster-datasource-fetched", { + datasource: this, + }); + + resolve(response); + }) + .catch((error) => { + fireCustomEvent(this, "monster-datasource-error", { + error: error, + }); + + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error.toString()); + reject(error); + }); + }, 0); + }); + } } /** * @private */ function removeFilter() { - const filterID = this.getOption("filter.id", undefined); - if (!filterID) return; + const filterID = this.getOption("filter.id", undefined); + if (!filterID) return; - const filterControl = findElementWithIdUpwards(this, filterID); + const filterControl = findElementWithIdUpwards(this, filterID); - if (filterControl && this[filterObserverSymbol]) { - filterControl?.detachObserver(this[filterObserverSymbol]); - } + if (filterControl && this[filterObserverSymbol]) { + filterControl?.detachObserver(this[filterObserverSymbol]); + } } /** * @private */ function initFilter() { - const filterID = this.getOption("filter.id", undefined); - - if (!filterID) - throw new Error("filter feature is enabled but no filter id is defined"); - - const filterControl = findElementWithIdUpwards(this, filterID); - if (!filterControl) - throw new Error( - "filter feature is enabled but no filter control with id " + - filterID + - " is found", - ); - - this[filterObserverSymbol] = new Observer(() => { - const query = filterControl.getOption("query"); - if (query === undefined) { - return; - } - this.setParameters({query: query}); - this.fetch() - .then((response) => { - if (!(response instanceof Response)) { - throw new Error("Response is not an instance of Response"); - } - - if (response?.ok === true) { - this.dispatchEvent(new CustomEvent("reload", {bubbles: true})); - filterControl?.showSuccess(); - } - - if (response.bodyUsed === true) { - return handleIntersectionObserver.call( - this, - response[rawDataSymbol], - response, - filterControl, - ); - } - - response - .text() - .then((jsonAsText) => { - let json; - try { - json = JSON.parse(jsonAsText); - } catch (e) { - const message = e instanceof Error ? e.message : `${e}`; - filterControl?.showFailureMessage(message); - return Promise.reject(e); - } - - return handleIntersectionObserver.call( - this, - json, - response, - filterControl, - ); - }) - .catch((e) => { - filterControl?.showFailureMessage(e.message); - }); - }) - .catch((e) => { - this.dispatchEvent( - new CustomEvent("error", {bubbles: true, detail: e}), - ); - - if (!(e instanceof Error)) { - e = new Error(e); - } - - filterControl?.showFailureMessage(e.message); - return Promise.reject(e); - }); - }); - - filterControl.attachObserver(this[filterObserverSymbol]); + const filterID = this.getOption("filter.id", undefined); + + if (!filterID) + throw new Error("filter feature is enabled but no filter id is defined"); + + const filterControl = findElementWithIdUpwards(this, filterID); + if (!filterControl) + throw new Error( + "filter feature is enabled but no filter control with id " + + filterID + + " is found", + ); + + this[filterObserverSymbol] = new Observer(() => { + const query = filterControl.getOption("query"); + if (query === undefined) { + return; + } + this.setParameters({ query: query }); + this.fetch() + .then((response) => { + if (!(response instanceof Response)) { + throw new Error("Response is not an instance of Response"); + } + + if (response?.ok === true) { + this.dispatchEvent(new CustomEvent("reload", { bubbles: true })); + filterControl?.showSuccess(); + } + + if (response.bodyUsed === true) { + return handleIntersectionObserver.call( + this, + response[rawDataSymbol], + response, + filterControl, + ); + } + + response + .text() + .then((jsonAsText) => { + let json; + try { + json = JSON.parse(jsonAsText); + } catch (e) { + const message = e instanceof Error ? e.message : `${e}`; + filterControl?.showFailureMessage(message); + return Promise.reject(e); + } + + return handleIntersectionObserver.call( + this, + json, + response, + filterControl, + ); + }) + .catch((e) => { + filterControl?.showFailureMessage(e.message); + }); + }) + .catch((e) => { + this.dispatchEvent( + new CustomEvent("error", { bubbles: true, detail: e }), + ); + + if (!(e instanceof Error)) { + e = new Error(e); + } + + filterControl?.showFailureMessage(e.message); + return Promise.reject(e); + }); + }); + + filterControl.attachObserver(this[filterObserverSymbol]); } function handleIntersectionObserver(json, response, filterControl) { - const path = new Pathfinder(json); - - const codePath = this.getOption("response.path.code"); - - if (path.exists(codePath)) { - const code = `${path.getVia(codePath)}`; - if (code && code === "200") { - filterControl?.showSuccess(); - return Promise.resolve(response); - } - - const messagePath = this.getOption("response.path.message"); - if (path.exists(messagePath)) { - const message = path.getVia(messagePath); - filterControl?.showFailureMessage(message); - return Promise.reject(new Error(message)); - } - - return Promise.reject(new Error("Response code is not 200")); - } + const path = new Pathfinder(json); + + const codePath = this.getOption("response.path.code"); + + if (path.exists(codePath)) { + const code = `${path.getVia(codePath)}`; + if (code && code === "200") { + filterControl?.showSuccess(); + return Promise.resolve(response); + } + + const messagePath = this.getOption("response.path.message"); + if (path.exists(messagePath)) { + const message = path.getVia(messagePath); + filterControl?.showFailureMessage(message); + return Promise.reject(new Error(message)); + } + + return Promise.reject(new Error("Response code is not 200")); + } } /** * @private */ function initAutoInit() { + const autoInit = this.getOption("features.autoInit"); + validateBoolean(autoInit); - const autoInit = this.getOption("features.autoInit"); - validateBoolean(autoInit); - - if (autoInit !== true) return; + if (autoInit !== true) return; - if (this.getOption("autoInit.intersectionObserver") === true) { - initIntersectionObserver.call(this); - return; - } + if (this.getOption("autoInit.intersectionObserver") === true) { + initIntersectionObserver.call(this); + return; + } - setTimeout(() => { - this.fetch().catch(() => { - }); - }, 0); + setTimeout(() => { + this.fetch().catch(() => {}); + }, 0); } function initEventHandler() { - this[intersectionObserverHandlerSymbol] = (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - if (entry.intersectionRatio > 0) { - this.fetch(); - } - - // only load once - if ( - this.getOption("autoInit.oneTime") === true && - this[intersectionObserverObserverSymbol] !== undefined - ) { - this[intersectionObserverObserverSymbol].unobserve(this); - } - } - }); - }; + this[intersectionObserverHandlerSymbol] = (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (entry.intersectionRatio > 0) { + this.fetch(); + } + + // only load once + if ( + this.getOption("autoInit.oneTime") === true && + this[intersectionObserverObserverSymbol] !== undefined + ) { + this[intersectionObserverObserverSymbol].unobserve(this); + } + } + }); + }; } function initIntersectionObserver() { - this.classList.add("intersection-observer"); - - const options = { - root: null, - rootMargin: "0px", - threshold: 0.1, - }; - - this[intersectionObserverObserverSymbol] = new IntersectionObserver( - this[intersectionObserverHandlerSymbol], - options, - ); - this[intersectionObserverObserverSymbol].observe(this); + this.classList.add("intersection-observer"); + + const options = { + root: null, + rootMargin: "0px", + threshold: 0.1, + }; + + this[intersectionObserverObserverSymbol] = new IntersectionObserver( + this[intersectionObserverHandlerSymbol], + options, + ); + this[intersectionObserverObserverSymbol].observe(this); } /** @@ -540,8 +538,8 @@ function initIntersectionObserver() { * @return {string} */ function getTemplate() { - // language=HTML - return ` + // language=HTML + return ` <slot></slot>`; } diff --git a/source/components/datatable/filter.mjs b/source/components/datatable/filter.mjs index 2247a316a..b125a85ff 100644 --- a/source/components/datatable/filter.mjs +++ b/source/components/datatable/filter.mjs @@ -238,7 +238,6 @@ class Filter extends CustomElement { }, }, labels: { - search: "Search", reset: "Reset", save: "Save", diff --git a/source/components/form/field-set.mjs b/source/components/form/field-set.mjs index 70e89935a..ec1e31cd6 100644 --- a/source/components/form/field-set.mjs +++ b/source/components/form/field-set.mjs @@ -12,25 +12,25 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {instanceSymbol} from "../../constants.mjs"; -import {addAttributeToken} from "../../dom/attributes.mjs"; +import { instanceSymbol } from "../../constants.mjs"; +import { addAttributeToken } from "../../dom/attributes.mjs"; import { - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_ROLE, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; -import {CustomControl} from "../../dom/customcontrol.mjs"; +import { CustomControl } from "../../dom/customcontrol.mjs"; import { - assembleMethodSymbol, - getSlottedElements, - registerCustomElement, + assembleMethodSymbol, + getSlottedElements, + registerCustomElement, } from "../../dom/customelement.mjs"; -import {isFunction} from "../../types/is.mjs"; -import {FieldSetStyleSheet} from "./stylesheet/field-set.mjs"; +import { isFunction } from "../../types/is.mjs"; +import { FieldSetStyleSheet } from "./stylesheet/field-set.mjs"; import "../layout/collapse.mjs"; import "./toggle-switch.mjs"; -export {FieldSet}; - +export { FieldSet }; + /** * @private * @type {symbol} @@ -101,178 +101,178 @@ const extendedSwitchElementSymbol = Symbol("extendedSwitchElement"); * @summary A simple FieldSet */ class FieldSet extends CustomControl { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/form/fieldset@@instance"); - } - - /** - * @return {Components.Form.FieldSet - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - initControlReferences.call(this); - initEventHandler.call(this); - updateExtendedFields.call(this); - return this; - } - - /** - * To set the options via the html tag the attribute `data-monster-options` must be used. - * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} - * - * The individual configuration values can be found in the table. - * - * @property {Object} templates Template definitions - * @property {string} templates.main Main template - * @property {Object} labels Label definitions - * @property {Object} actions Callbacks - * @property {string} actions.click="throw Error" Callback when clicked - * @property {Object} features Features - * @property {Object} classes CSS classes - * @property {boolean} disabled=false Disabled state - */ - get defaults() { - return Object.assign({}, super.defaults, { - templates: { - main: getTemplate(), - }, - labels: { - "toggle-switch-on": "✔", - "toggle-switch-off": "✖", - "toggle-switch-label": "Expand", - title: "", - }, - classes: {}, - disabled: false, - features: {}, - actions: { - click: () => { - throw new Error("the click action is not defined"); - }, - }, - value: null, - }); - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-field-set"; - } - - /** - * - * @return {CSSStyleSheet[]} - */ - static getCSSStyleSheet() { - return [FieldSetStyleSheet]; - } - - /** - * The FieldSet.click() method simulates a click on the internal element. - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} - */ - click() { - if (this.getOption("disabled") === true) { - return; - } - - if ( - this[fieldSetElementSymbol] && - isFunction(this[fieldSetElementSymbol].click) - ) { - this[fieldSetElementSymbol].click(); - } - } - - /** - * The Button.focus() method sets focus on the internal element. - * - * @param {Object} options - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} - */ - focus(options) { - if (this.getOption("disabled") === true) { - return; - } - - if ( - this[fieldSetElementSymbol] && - isFunction(this[fieldSetElementSymbol].focus) - ) { - this[fieldSetElementSymbol].focus(options); - } - } - - /** - * The Button.blur() method removes focus from the internal element. - */ - blur() { - if ( - this[fieldSetElementSymbol] && - isFunction(this[fieldSetElementSymbol].blur) - ) { - this[fieldSetElementSymbol].blur(); - } - } - - /** - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @return {boolean} - */ - static get formAssociated() { - return true; - } - - /** - * The current value of the form control. - * - * ```js - * e = document.querySelector('monster-field-set'); - * console.log(e.value) - * ``` - * - * @property {string} - */ - get value() { - return this.getOption("value"); - } - - /** - * Set value of the form control. - * - * ``` - * e = document.querySelector('monster-field-set'); - * e.value=1 - * ``` - * - * @property {string} value - * @throws {Error} unsupported type - */ - set value(value) { - this.setOption("value", value); - try { - this?.setFormValue(this.value); - } catch (e) { - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message); - } - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/form/fieldset@@instance"); + } + + /** + * @return {Components.Form.FieldSet + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + initControlReferences.call(this); + initEventHandler.call(this); + updateExtendedFields.call(this); + return this; + } + + /** + * To set the options via the html tag the attribute `data-monster-options` must be used. + * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {Object} labels Label definitions + * @property {Object} actions Callbacks + * @property {string} actions.click="throw Error" Callback when clicked + * @property {Object} features Features + * @property {Object} classes CSS classes + * @property {boolean} disabled=false Disabled state + */ + get defaults() { + return Object.assign({}, super.defaults, { + templates: { + main: getTemplate(), + }, + labels: { + "toggle-switch-on": "✔", + "toggle-switch-off": "✖", + "toggle-switch-label": "Expand", + title: "", + }, + classes: {}, + disabled: false, + features: {}, + actions: { + click: () => { + throw new Error("the click action is not defined"); + }, + }, + value: null, + }); + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-field-set"; + } + + /** + * + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [FieldSetStyleSheet]; + } + + /** + * The FieldSet.click() method simulates a click on the internal element. + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click} + */ + click() { + if (this.getOption("disabled") === true) { + return; + } + + if ( + this[fieldSetElementSymbol] && + isFunction(this[fieldSetElementSymbol].click) + ) { + this[fieldSetElementSymbol].click(); + } + } + + /** + * The Button.focus() method sets focus on the internal element. + * + * @param {Object} options + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus} + */ + focus(options) { + if (this.getOption("disabled") === true) { + return; + } + + if ( + this[fieldSetElementSymbol] && + isFunction(this[fieldSetElementSymbol].focus) + ) { + this[fieldSetElementSymbol].focus(options); + } + } + + /** + * The Button.blur() method removes focus from the internal element. + */ + blur() { + if ( + this[fieldSetElementSymbol] && + isFunction(this[fieldSetElementSymbol].blur) + ) { + this[fieldSetElementSymbol].blur(); + } + } + + /** + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} + * @return {boolean} + */ + static get formAssociated() { + return true; + } + + /** + * The current value of the form control. + * + * ```js + * e = document.querySelector('monster-field-set'); + * console.log(e.value) + * ``` + * + * @property {string} + */ + get value() { + return this.getOption("value"); + } + + /** + * Set value of the form control. + * + * ``` + * e = document.querySelector('monster-field-set'); + * e.value=1 + * ``` + * + * @property {string} value + * @throws {Error} unsupported type + */ + set value(value) { + this.setOption("value", value); + try { + this?.setFormValue(this.value); + } catch (e) { + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message); + } + } } function updateExtendedFields() { - const nodes = getSlottedElements.call(this, "", "extended"); - if (nodes.size > 0) { - this[extendedSwitchSymbol].classList.remove("hidden"); - } else { - this[extendedSwitchSymbol].classList.add("hidden"); - } + const nodes = getSlottedElements.call(this, "", "extended"); + if (nodes.size > 0) { + this[extendedSwitchSymbol].classList.remove("hidden"); + } else { + this[extendedSwitchSymbol].classList.add("hidden"); + } } /** @@ -281,53 +281,53 @@ function updateExtendedFields() { * @fires Monster.Components.Form.event:monster-field-set-clicked */ function initEventHandler() { - this[toggleSwitchElementSymbol].setOption( - "labels.toggle-switch-on", - this.getOption("labels.toggle-switch-on"), - ); - this[toggleSwitchElementSymbol].setOption( - "labels.toggle-switch-off", - this.getOption("labels.toggle-switch-off"), - ); - - this[toggleSwitchElementSymbol].setOption("actions.on", () => { - this[collapseElementSymbol].open(); - }); - - this[toggleSwitchElementSymbol].setOption("actions.off", () => { - this[collapseElementSymbol].close(); - }); - - return this; + this[toggleSwitchElementSymbol].setOption( + "labels.toggle-switch-on", + this.getOption("labels.toggle-switch-on"), + ); + this[toggleSwitchElementSymbol].setOption( + "labels.toggle-switch-off", + this.getOption("labels.toggle-switch-off"), + ); + + this[toggleSwitchElementSymbol].setOption("actions.on", () => { + this[collapseElementSymbol].open(); + }); + + this[toggleSwitchElementSymbol].setOption("actions.off", () => { + this[collapseElementSymbol].close(); + }); + + return this; } /** * @private */ function initControlReferences() { - this[fieldSetElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}="control"]`, - ); + this[fieldSetElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}="control"]`, + ); - this[extendedSwitchElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}="extended-switch"]`, - ); + this[extendedSwitchElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}="extended-switch"]`, + ); - this[collapseElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}="collapse"]`, - ); + this[collapseElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}="collapse"]`, + ); - this[headerElementSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}="header"]`, - ); + this[headerElementSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}="header"]`, + ); - this[extendedSwitchSymbol] = this.shadowRoot.querySelector( - `[${ATTRIBUTE_ROLE}="extended-switch"]`, - ); + this[extendedSwitchSymbol] = this.shadowRoot.querySelector( + `[${ATTRIBUTE_ROLE}="extended-switch"]`, + ); - this[toggleSwitchElementSymbol] = this.shadowRoot.querySelector( - `monster-toggle-switch`, - ); + this[toggleSwitchElementSymbol] = this.shadowRoot.querySelector( + `monster-toggle-switch`, + ); } /** @@ -335,8 +335,8 @@ function initControlReferences() { * @return {string} */ function getTemplate() { - // language=HTML - return ` + // language=HTML + return ` <div data-monster-role="control" part="control"> <div data-monster-role="header"> <div data-monster-replace="path:labels.title" data-monster-role="title"></div> diff --git a/source/components/form/form.mjs b/source/components/form/form.mjs index 7d7f02ead..dc64c68f1 100644 --- a/source/components/form/form.mjs +++ b/source/components/form/form.mjs @@ -12,49 +12,16 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {instanceSymbol} from "../../constants.mjs"; -import {internalSymbol} from "../../constants.mjs"; -import {TokenList} from "../../types/tokenlist.mjs"; -import {DeadMansSwitch} from "../../util/deadmansswitch.mjs"; -import {DataSet} from "../datatable/dataset.mjs"; -//import { Datasource } from "../../data/datasource.mjs"; -import {RestAPI} from "../../data/datasource/server/restapi.mjs"; -import {WebConnect} from "../../data/datasource/server/webconnect.mjs"; -import {WriteError} from "../../data/datasource/server/restapi/writeerror.mjs"; -import {LocalStorage} from "../../data/datasource/storage/localstorage.mjs"; -import {SessionStorage} from "../../data/datasource/storage/sessionstorage.mjs"; +import { DeadMansSwitch } from "../../util/deadmansswitch.mjs"; +import { DataSet } from "../datatable/dataset.mjs"; import { - ATTRIBUTE_DISABLED, - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_PREFIX, - ATTRIBUTE_UPDATER_ATTRIBUTES, - ATTRIBUTE_UPDATER_INSERT, - ATTRIBUTE_UPDATER_REMOVE, - ATTRIBUTE_UPDATER_REPLACE, -} from "../../dom/constants.mjs"; -import { - assembleMethodSymbol, - CustomElement, - registerCustomElement, - getSlottedElements, + assembleMethodSymbol, + registerCustomElement, + getSlottedElements, } from "../../dom/customelement.mjs"; -import {addObjectWithUpdaterToElement} from "../../dom/updater.mjs"; -import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; -import {isFunction, isString} from "../../types/is.mjs"; -import {Observer} from "../../types/observer.mjs"; -import {ProxyObserver} from "../../types/proxyobserver.mjs"; -import {Processing} from "../../util/processing.mjs"; -import {datasourceLinkedElementSymbol, handleDataSourceChanges} from "../datatable/util.mjs"; -import {MessageStateButton} from "./message-state-button.mjs"; -import { - ATTRIBUTE_FORM_DATASOURCE, - ATTRIBUTE_FORM_DATASOURCE_ARGUMENTS, -} from "./constants.mjs"; -import {StateButton} from "./state-button.mjs"; -import {FormStyleSheet} from "./stylesheet/form.mjs"; - -export {Form}; +import { FormStyleSheet } from "./stylesheet/form.mjs"; +export { Form }; /** * @private @@ -63,155 +30,138 @@ export {Form}; const debounceCallbackSymbol = Symbol("timerCallback"); class Form extends DataSet { - - /** - * - * @returns {{shadowMode: string, templates: {main: *}, display: string, disabled: boolean, delegatesFocus: boolean, templateMapping: {}} & {templates: {main: string}, classes: {form: string}}} - */ - get defaults() { - const obj = Object.assign( - {}, - super.defaults, - { - templates: { - main: getTemplate(), - }, - - classes: { - form: "", - }, - - writeBack: { - events: ["change", "input", "keyup"] - }, - - reportValidity: { - selector: "input,select,textarea", - } - - } - ); - - obj['features']['mutationObserver'] = false; - obj['features']['writeBack'] = true; - - return obj; - - } - - - /** - * - * @return {string} - */ - static getTag() { - return "monster-form"; - } - - /** - * @return {CSSStyleSheet[]} - */ - static getCSSStyleSheet() { - return [FormStyleSheet]; - } - - /** - * - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - - initControlReferences.call(this); - initEventHandler.call(this); - initDataSourceHandler.call(this); - - } - - /** - * This method is called when the component is created. - * @since 3.70.0 - * @returns {DataSet} - */ - refresh() { - this.write(); - super.refresh(); - return this; - } - - /** - * Run reportValidation on all child html form controls. - * - * @since 2.10.0 - * @returns {boolean} - */ - reportValidity() { - let valid = true; - - const selector = this.getOption("reportValidity.selector"); - const nodes = getSlottedElements.call(this, selector); - - nodes.forEach((node) => { - if (typeof node.reportValidity === "function") { - if (node.reportValidity() === false) { - valid = false; - } - } - }); - - return valid; - } - + /** + * + * @returns {{shadowMode: string, templates: {main: *}, display: string, disabled: boolean, delegatesFocus: boolean, templateMapping: {}} & {templates: {main: string}, classes: {form: string}}} + */ + get defaults() { + const obj = Object.assign({}, super.defaults, { + templates: { + main: getTemplate(), + }, + + classes: { + form: "", + }, + + writeBack: { + events: ["change", "input", "keyup"], + }, + + reportValidity: { + selector: "input,select,textarea", + }, + }); + + obj["features"]["mutationObserver"] = false; + obj["features"]["writeBack"] = true; + + return obj; + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-form"; + } + + /** + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [FormStyleSheet]; + } + + /** + * + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + + initControlReferences.call(this); + initEventHandler.call(this); + initDataSourceHandler.call(this); + } + + /** + * This method is called when the component is created. + * @since 3.70.0 + * @returns {DataSet} + */ + refresh() { + this.write(); + super.refresh(); + return this; + } + + /** + * Run reportValidation on all child html form controls. + * + * @since 2.10.0 + * @returns {boolean} + */ + reportValidity() { + let valid = true; + + const selector = this.getOption("reportValidity.selector"); + const nodes = getSlottedElements.call(this, selector); + + nodes.forEach((node) => { + if (typeof node.reportValidity === "function") { + if (node.reportValidity() === false) { + valid = false; + } + } + }); + + return valid; + } } -function initDataSourceHandler() { - -} +function initDataSourceHandler() {} /** * @private * @returns {initEventHandler} */ function initEventHandler() { - - if (this.getOption("features.writeBack") === true) { - const events = this.getOption("writeBack.events"); - for (const event of events) { - - this.addEventListener(event, (e) => { - - if (!this.reportValidity()) { - - this.classList.add("invalid"); - setTimeout(() => { - this.classList.remove("invalid"); - }, 1000) - - return; - } - - if (this[debounceCallbackSymbol] instanceof DeadMansSwitch) { - try { - this[debounceCallbackSymbol].touch(); - return; - } catch (e) { - if (e.message !== "has already run") { - throw e; - } - delete this[debounceCallbackSymbol]; - } - } - - this[debounceCallbackSymbol] = new DeadMansSwitch(200, () => { - setTimeout(() => { - this.write(); - }, 0); - }); - - }); - } - } - - return this; + if (this.getOption("features.writeBack") === true) { + const events = this.getOption("writeBack.events"); + for (const event of events) { + this.addEventListener(event, (e) => { + if (!this.reportValidity()) { + this.classList.add("invalid"); + setTimeout(() => { + this.classList.remove("invalid"); + }, 1000); + + return; + } + + if (this[debounceCallbackSymbol] instanceof DeadMansSwitch) { + try { + this[debounceCallbackSymbol].touch(); + return; + } catch (e) { + if (e.message !== "has already run") { + throw e; + } + delete this[debounceCallbackSymbol]; + } + } + + this[debounceCallbackSymbol] = new DeadMansSwitch(200, () => { + setTimeout(() => { + this.write(); + }, 0); + }); + }); + } + } + + return this; } /** @@ -219,10 +169,10 @@ function initEventHandler() { * @return {FilterButton} */ function initControlReferences() { - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - return this; + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + return this; } /** @@ -230,8 +180,8 @@ function initControlReferences() { * @return {string} */ function getTemplate() { - // language=HTML - return ` + // language=HTML + return ` <div data-monster-role="control" part="control"> <form data-monster-attributes="disabled path:disabled | if:true, class path:classes.form" data-monster-role="form" diff --git a/source/components/form/reload.mjs b/source/components/form/reload.mjs index 439af80e0..3d27a63ad 100644 --- a/source/components/form/reload.mjs +++ b/source/components/form/reload.mjs @@ -12,24 +12,24 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {instanceSymbol} from "../../constants.mjs"; -import {addAttributeToken} from "../../dom/attributes.mjs"; +import { instanceSymbol } from "../../constants.mjs"; +import { addAttributeToken } from "../../dom/attributes.mjs"; import { - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_ROLE, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; import { - assembleMethodSymbol, - attributeObserverSymbol, - CustomElement, - initMethodSymbol, - registerCustomElement, + assembleMethodSymbol, + attributeObserverSymbol, + CustomElement, + initMethodSymbol, + registerCustomElement, } from "../../dom/customelement.mjs"; -import {isString} from "../../types/is.mjs"; -import {ATTRIBUTE_FORM_RELOAD, ATTRIBUTE_FORM_URL} from "./constants.mjs"; -import {loadAndAssignContent} from "./util/fetch.mjs"; +import { isString } from "../../types/is.mjs"; +import { ATTRIBUTE_FORM_RELOAD, ATTRIBUTE_FORM_URL } from "./constants.mjs"; +import { loadAndAssignContent } from "./util/fetch.mjs"; -export {Reload}; +export { Reload }; /** * @private @@ -106,128 +106,128 @@ const intersectionObserverWasInitialized = Symbol("wasInitialized"); * @fires Monster.Components.event:monster-fetched */ class Reload extends CustomElement { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/form/reload"); - } - - /** - * To set the options via the html tag the attribute `data-monster-options` must be used. - * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} - * - * The individual configuration values can be found in the table. - * - * @property {Object} templates Template definitions - * @property {string} templates.main Main template - * @property {string} url=undefined - * @property {string} reload=undefined currently the values defined are `onshow` and `always`. The default `onshow` removes the IntersectionObserver. This means that the content is only loaded once. reloading of the content does not occur. - * @property {string} filter=undefined dom selectors to search for elements, if undefined then everything is taken - * @property {Monster.Components.Form.Processor[]} processors - * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) - * @property {String} fetch.redirect=error - * @property {String} fetch.method=GET - * @property {String} fetch.mode=same-origin - * @property {String} fetch.credentials=same-origin - * @property {Object} fetch.headers={"accept":"text/html"}} - */ - get defaults() { - return Object.assign( - {}, - super.defaults, - { - templates: { - main: getTemplate.call(this), - }, - shadowMode: null, - url: null, - reload: "onshow", - filter: null, - fetch: { - redirect: "error", - method: "GET", - mode: "same-origin", - credentials: "same-origin", - headers: { - accept: "text/html", - }, - }, - }, - initOptionsFromArguments.call(this), - ); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - */ - static get observedAttributes() { - const list = super.observedAttributes; - list.push(ATTRIBUTE_FORM_URL); - return list; - } - - /** - * @return {void} - */ - [initMethodSymbol]() { - super[initMethodSymbol](); - - // data-monster-options - this[attributeObserverSymbol][ATTRIBUTE_FORM_URL] = (url) => { - if (this.hasAttribute(ATTRIBUTE_FORM_URL)) { - this.setOption("url", new URL(url, document.location).toString()); - } else { - this.setOption("url", undefined); - } - }; - } - - /** - * This method is called internal and should not be called directly. - * @throws {Error} missing default slot - * @throws {Error} no shadow-root is defined - * @throws {Error} missing url - * @throws {Error} we won't be able to read the data - * @throws {Error} request failed - * @throws {Error} not found - * @throws {Error} undefined status or type - * @fires Monster.Components.event:monster-fetched - * @return {Monster.Components.Form.Form} - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - initIntersectionObserver.call(this); - } - - /** - * This method is called internal and should not be called directly. - * - * @return {string} - */ - static getTag() { - return "monster-reload"; - } - - /** - * load content from url - * - * It is important to know that with this function the loading is executed - * directly. it is loaded as well when the element is not visible. - * - * @param {string|undefined} url - */ - fetch(url) { - if (isString(url) || url instanceof URL) { - this.setAttribute(ATTRIBUTE_FORM_URL, `${url}`); - } - - return loadContent.call(this); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/form/reload"); + } + + /** + * To set the options via the html tag the attribute `data-monster-options` must be used. + * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {string} url=undefined + * @property {string} reload=undefined currently the values defined are `onshow` and `always`. The default `onshow` removes the IntersectionObserver. This means that the content is only loaded once. reloading of the content does not occur. + * @property {string} filter=undefined dom selectors to search for elements, if undefined then everything is taken + * @property {Monster.Components.Form.Processor[]} processors + * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) + * @property {String} fetch.redirect=error + * @property {String} fetch.method=GET + * @property {String} fetch.mode=same-origin + * @property {String} fetch.credentials=same-origin + * @property {Object} fetch.headers={"accept":"text/html"}} + */ + get defaults() { + return Object.assign( + {}, + super.defaults, + { + templates: { + main: getTemplate.call(this), + }, + shadowMode: null, + url: null, + reload: "onshow", + filter: null, + fetch: { + redirect: "error", + method: "GET", + mode: "same-origin", + credentials: "same-origin", + headers: { + accept: "text/html", + }, + }, + }, + initOptionsFromArguments.call(this), + ); + } + + /** + * This method determines which attributes are to be monitored by `attributeChangedCallback()`. + * + * @return {string[]} + */ + static get observedAttributes() { + const list = super.observedAttributes; + list.push(ATTRIBUTE_FORM_URL); + return list; + } + + /** + * @return {void} + */ + [initMethodSymbol]() { + super[initMethodSymbol](); + + // data-monster-options + this[attributeObserverSymbol][ATTRIBUTE_FORM_URL] = (url) => { + if (this.hasAttribute(ATTRIBUTE_FORM_URL)) { + this.setOption("url", new URL(url, document.location).toString()); + } else { + this.setOption("url", undefined); + } + }; + } + + /** + * This method is called internal and should not be called directly. + * @throws {Error} missing default slot + * @throws {Error} no shadow-root is defined + * @throws {Error} missing url + * @throws {Error} we won't be able to read the data + * @throws {Error} request failed + * @throws {Error} not found + * @throws {Error} undefined status or type + * @fires Monster.Components.event:monster-fetched + * @return {Monster.Components.Form.Form} + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + initIntersectionObserver.call(this); + } + + /** + * This method is called internal and should not be called directly. + * + * @return {string} + */ + static getTag() { + return "monster-reload"; + } + + /** + * load content from url + * + * It is important to know that with this function the loading is executed + * directly. it is loaded as well when the element is not visible. + * + * @param {string|undefined} url + */ + fetch(url) { + if (isString(url) || url instanceof URL) { + this.setAttribute(ATTRIBUTE_FORM_URL, `${url}`); + } + + return loadContent.call(this); + } } /** @@ -241,19 +241,19 @@ class Reload extends CustomElement { * @return {object} */ function initOptionsFromArguments() { - const options = {}; + const options = {}; - const url = this.getAttribute(ATTRIBUTE_FORM_URL); + const url = this.getAttribute(ATTRIBUTE_FORM_URL); - if (isString(url)) { - options["url"] = new URL(url, document.location).toString(); - } + if (isString(url)) { + options["url"] = new URL(url, document.location).toString(); + } - if (this.hasAttribute(ATTRIBUTE_FORM_RELOAD)) { - options["reload"] = this.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase(); - } + if (this.hasAttribute(ATTRIBUTE_FORM_RELOAD)) { + options["reload"] = this.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase(); + } - return options; + return options; } /** @@ -268,37 +268,37 @@ function initOptionsFromArguments() { * @fires Monster.Components.event:monster-fetched */ function initIntersectionObserver() { - if (this[intersectionObserverWasInitialized] === true) { - return; - } - - this[intersectionObserverWasInitialized] = true; - - const options = { - threshold: [0.5], - }; - - const callback = (entries, observer) => { - for (const [, entry] of entries.entries()) { - if (entry.isIntersecting === true) { - // undefined or always do the same - if (this.getOption("reload") === "onshow") { - observer.disconnect(); - } - - try { - loadContent.call(this).catch((e) => { - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); - }); - } catch (e) { - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - } - }; - - const observer = new IntersectionObserver(callback, options); - observer.observe(this); + if (this[intersectionObserverWasInitialized] === true) { + return; + } + + this[intersectionObserverWasInitialized] = true; + + const options = { + threshold: [0.5], + }; + + const callback = (entries, observer) => { + for (const [, entry] of entries.entries()) { + if (entry.isIntersecting === true) { + // undefined or always do the same + if (this.getOption("reload") === "onshow") { + observer.disconnect(); + } + + try { + loadContent.call(this).catch((e) => { + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); + }); + } catch (e) { + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); + } + } + } + }; + + const observer = new IntersectionObserver(callback, options); + observer.observe(this); } /** @@ -318,41 +318,41 @@ function initIntersectionObserver() { * @return {Promise} */ function loadContent() { - const url = this.getOption("url", undefined); - if (!isString(url) || url === "") { - throw new Error("missing url"); - } - - const options = this.getOption("fetch", {}); - - let parentNode = this; - if (this.shadowRoot) { - parentNode = this.shadowRoot; - } - - let container = parentNode.querySelector(`[${ATTRIBUTE_ROLE}=container]`); - let currentDisplayMode = container?.style?.display; - - if (currentDisplayMode === undefined) { - currentDisplayMode = "inherit"; - } - - if (!(container instanceof HTMLElement)) { - container = document.createElement("div"); - container.style.display = "none"; - container.setAttribute(ATTRIBUTE_ROLE, "container"); - parentNode.appendChild(container); - } - - return loadAndAssignContent(container, url, options, this.getOption("filter")) - .then(() => { - if (currentDisplayMode !== undefined) { - container.style.display = currentDisplayMode; - } - }) - .catch((e) => { - throw e; - }); + const url = this.getOption("url", undefined); + if (!isString(url) || url === "") { + throw new Error("missing url"); + } + + const options = this.getOption("fetch", {}); + + let parentNode = this; + if (this.shadowRoot) { + parentNode = this.shadowRoot; + } + + let container = parentNode.querySelector(`[${ATTRIBUTE_ROLE}=container]`); + let currentDisplayMode = container?.style?.display; + + if (currentDisplayMode === undefined) { + currentDisplayMode = "inherit"; + } + + if (!(container instanceof HTMLElement)) { + container = document.createElement("div"); + container.style.display = "none"; + container.setAttribute(ATTRIBUTE_ROLE, "container"); + parentNode.appendChild(container); + } + + return loadAndAssignContent(container, url, options, this.getOption("filter")) + .then(() => { + if (currentDisplayMode !== undefined) { + container.style.display = currentDisplayMode; + } + }) + .catch((e) => { + throw e; + }); } /** @@ -360,7 +360,7 @@ function loadContent() { * @return {string} */ function getTemplate() { - return this.innerHTML; + return this.innerHTML; } registerCustomElement(Reload); diff --git a/source/components/form/select.mjs b/source/components/form/select.mjs index 57a1e9f63..44ae5a2d4 100644 --- a/source/components/form/select.mjs +++ b/source/components/form/select.mjs @@ -698,7 +698,7 @@ class Select extends CustomControl { const selection = this.getOption("selection"); let newValue = []; if (selection) { - newValue = selection + newValue = selection; } else if (this.hasAttribute("value")) { newValue = this.getAttribute("value"); } @@ -710,7 +710,7 @@ class Select extends CustomControl { resolve(result); }, 10); - return + return; } setStatusOrRemoveBadges.call(this, "error"); @@ -1866,7 +1866,6 @@ function areOptionsAvailableAndInit() { * @throws {Error} no shadow-root is defined */ function checkOptionState() { - if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } @@ -1891,8 +1890,6 @@ function checkOptionState() { if (e.checked !== false) e.checked = false; } } - - } /** @@ -1976,8 +1973,8 @@ function setSelection(selection) { selection = []; } - validateArray(selection) - + validateArray(selection); + for (let i = 0; i < selection.length; i++) { selection[i] = { label: getSelectionLabel.call(this, selection[i].value), @@ -2112,14 +2109,16 @@ function show() { if (lazyLoadFlag === true) { this[lazyLoadDoneSymbol] = true; setStatusOrRemoveBadges.call(this, "loading"); - + new Processing(200, () => { - this.fetch().then(() => { + this.fetch() + .then(() => { checkOptionState.call(this); - }).catch((e) => { - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message); - setStatusOrRemoveBadges.call(this, "error"); - }); + }) + .catch((e) => { + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message); + setStatusOrRemoveBadges.call(this, "error"); + }); }) .run() .catch((e) => { diff --git a/source/data/datasource/server.mjs b/source/data/datasource/server.mjs index d7dc4ca5e..d0cfe053b 100644 --- a/source/data/datasource/server.mjs +++ b/source/data/datasource/server.mjs @@ -12,15 +12,14 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {instanceSymbol} from "../../constants.mjs"; -import {isArray, isFunction, isObject} from "../../types/is.mjs"; -import {Datasource} from "../datasource.mjs"; -import {diff} from "../diff.mjs"; -import {Pathfinder} from "../pathfinder.mjs"; -import {Pipe} from "../pipe.mjs"; - -export {Server}; +import { instanceSymbol } from "../../constants.mjs"; +import { isArray, isFunction, isObject } from "../../types/is.mjs"; +import { Datasource } from "../datasource.mjs"; +import { diff } from "../diff.mjs"; +import { Pathfinder } from "../pathfinder.mjs"; +import { Pipe } from "../pipe.mjs"; +export { Server }; /** * @private @@ -38,82 +37,85 @@ const serverVersionSymbol = Symbol("serverVersion"); * @summary The Server class encapsulates the access to a server datasource */ class Server extends Datasource { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/server"); - } - - /** - * This prepares the data that comes from the server. - * Should not be called directly. - * - * @private - * @param {Object} payload - * @returns {Object} - */ - transformServerPayload(payload) { - payload = doTransform.call(this, "read", payload); - this[serverVersionSymbol] = payload; - - const dataPath = this.getOption("read.path"); - if (dataPath) { - payload = new Pathfinder(payload).getVia(dataPath); - } - - return payload; - } - - /** - * This prepares the data for writing and should not be called directly. - * - * @private - * @param {Object} payload - * @returns {Object} - */ - prepareServerPayload(payload) { - payload = doTransform.call(this, "write", payload); - payload = doDiff.call(this, payload); - - const sheathingObject = this.getOption("write.sheathing.object"); - const sheathingPath = this.getOption("write.sheathing.path"); - - if (sheathingObject && sheathingPath) { - const sub = payload; - payload = sheathingObject; - new Pathfinder(payload).setVia(sheathingPath, sub); - } - - return payload; - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/server"); + } + + /** + * This prepares the data that comes from the server. + * Should not be called directly. + * + * @private + * @param {Object} payload + * @returns {Object} + */ + transformServerPayload(payload) { + payload = doTransform.call(this, "read", payload); + this[serverVersionSymbol] = payload; + + const dataPath = this.getOption("read.path"); + if (dataPath) { + payload = new Pathfinder(payload).getVia(dataPath); + } + + return payload; + } + + /** + * This prepares the data for writing and should not be called directly. + * + * @private + * @param {Object} payload + * @returns {Object} + */ + prepareServerPayload(payload) { + payload = doTransform.call(this, "write", payload); + payload = doDiff.call(this, payload); + + const sheathingObject = this.getOption("write.sheathing.object"); + const sheathingPath = this.getOption("write.sheathing.path"); + + if (sheathingObject && sheathingPath) { + const sub = payload; + payload = sheathingObject; + new Pathfinder(payload).setVia(sheathingPath, sub); + } + + return payload; + } } /** - * + * * @param obj * @returns {*} */ function doDiff(obj) { - if (this[serverVersionSymbol] === null || this[serverVersionSymbol] === undefined) { - return obj; - } - - const callback = this.getOption("write.partial.callback"); - if (!isFunction(callback)) { - return obj; - } - - const results = diff(this[serverVersionSymbol], obj); - if (!results) { - return obj; - } - - obj = callback(obj, results); - this[serverVersionSymbol] = obj; - - return obj; + if ( + this[serverVersionSymbol] === null || + this[serverVersionSymbol] === undefined + ) { + return obj; + } + + const callback = this.getOption("write.partial.callback"); + if (!isFunction(callback)) { + return obj; + } + + const results = diff(this[serverVersionSymbol], obj); + if (!results) { + return obj; + } + + obj = callback(obj, results); + this[serverVersionSymbol] = obj; + + return obj; } /** @@ -123,32 +125,32 @@ function doDiff(obj) { * @returns {Object} */ function doTransform(type, obj) { - const transformation = this.getOption(`${type}.mapping.transformer`); - if (transformation !== undefined && transformation !== null) { - const pipe = new Pipe(transformation); - const callbacks = this.getOption(`${type}.mapping.callbacks`); - - if (isArray(callbacks)) { - for (const callback of callbacks) { - if (typeof callback === "function") { - pipe.setCallback(callback); - } - } - } - - if (isObject(callbacks)) { - for (const key in callbacks) { - if ( - callbacks.hasOwnProperty(key) && - typeof callbacks[key] === "function" - ) { - pipe.setCallback(key, callbacks[key]); - } - } - } - - obj = pipe.run(obj); - } - - return obj; + const transformation = this.getOption(`${type}.mapping.transformer`); + if (transformation !== undefined && transformation !== null) { + const pipe = new Pipe(transformation); + const callbacks = this.getOption(`${type}.mapping.callbacks`); + + if (isArray(callbacks)) { + for (const callback of callbacks) { + if (typeof callback === "function") { + pipe.setCallback(callback); + } + } + } + + if (isObject(callbacks)) { + for (const key in callbacks) { + if ( + callbacks.hasOwnProperty(key) && + typeof callbacks[key] === "function" + ) { + pipe.setCallback(key, callbacks[key]); + } + } + } + + obj = pipe.run(obj); + } + + return obj; } diff --git a/source/data/datasource/server/restapi.mjs b/source/data/datasource/server/restapi.mjs index d6f92235f..e2ab688f4 100644 --- a/source/data/datasource/server/restapi.mjs +++ b/source/data/datasource/server/restapi.mjs @@ -12,14 +12,14 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {internalSymbol, instanceSymbol} from "../../../constants.mjs"; -import {isObject, isFunction, isArray} from "../../../types/is.mjs"; -import {diff} from "../../diff.mjs"; -import {Server} from "../server.mjs"; -import {WriteError} from "./restapi/writeerror.mjs"; -import {DataFetchError} from "./restapi/data-fetch-error.mjs"; +import { internalSymbol, instanceSymbol } from "../../../constants.mjs"; +import { isObject, isFunction, isArray } from "../../../types/is.mjs"; +import { diff } from "../../diff.mjs"; +import { Server } from "../server.mjs"; +import { WriteError } from "./restapi/writeerror.mjs"; +import { DataFetchError } from "./restapi/data-fetch-error.mjs"; -export {RestAPI}; +export { RestAPI }; /** * @type {symbol} @@ -28,10 +28,9 @@ export {RestAPI}; * @since 3.12.0 */ const rawDataSymbol = Symbol.for( - "@schukai/monster/data/datasource/server/restapi/rawdata", + "@schukai/monster/data/datasource/server/restapi/rawdata", ); - /** * The RestAPI is a class that enables a REST API server. * @@ -43,145 +42,145 @@ const rawDataSymbol = Symbol.for( * @summary The RestAPI is a class that binds a REST API server. */ class RestAPI extends Server { - /** - * - * @param {Object} [options] options contains definitions for the datasource. - */ - constructor(options) { - super(); - - if (isObject(options)) { - this.setOptions(options); - } - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/server/restapi"); - } - - /** - * @property {Object} write={} Options - * @property {Object} write.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} - * @property {string} write.init.method=POST - * @property {Object} write.init.headers Object containing any custom headers that you want to apply to the request. - * @property {string} write.responseCallback Callback function to be executed after the request has been completed. - * @property {string} write.acceptedStatus=[200,201] - * @property {string} write.url URL - * @property {Object} write.mapping the mapping is applied before writing. - * @property {String} write.mapping.transformer Transformer to select the appropriate entries - * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. - * @property {Object} write.report - * @property {String} write.report.path Path to validations - * @property {Object} write.partial - * @property {Function} write.partial.callback Callback function to be executed after the request has been completed. (obj, diffResult) => obj - * @property {Object} write.sheathing - * @property {Object} write.sheathing.object Object to be wrapped - * @property {string} write.sheathing.path Path to the data - * @property {Object} read={} Options - * @property {Object} read.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} - * @property {string} read.init.method=GET - * @property {string} read.acceptedStatus=[200] - * @property {string} read.url URL - * @property {Object} read.mapping the mapping is applied after reading. - * @property {String} read.mapping.transformer Transformer to select the appropriate entries - * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading. - */ - get defaults() { - return Object.assign({}, super.defaults, { - write: { - init: { - method: "POST", - }, - responseCallback: undefined, - acceptedStatus: [200, 201], - url: null, - mapping: { - transformer: undefined, - callbacks: [], - }, - sheathing: { - object: undefined, - path: undefined, - }, - report: { - path: undefined, - }, - - partial: { - callback: null, - } - }, - read: { - init: { - method: "GET", - }, - responseCallback: undefined, - acceptedStatus: [200], - url: null, - mapping: { - transformer: undefined, - callbacks: [], - }, - }, - }); - } - - /** - * @return {Promise} - * @throws {Error} the options does not contain a valid json definition - * @throws {TypeError} value is not a object - * @throws {Error} the data cannot be read - */ - read() { - let init = this.getOption("read.init"); - if (!isObject(init)) init = {}; - if (!init["method"]) init["method"] = "GET"; - - let callback = this.getOption("read.responseCallback"); - if (!callback) { - callback = (obj) => { - this.set(this.transformServerPayload.call(this, obj)); - }; - } - - return fetchData.call(this, init, "read", callback); - } - - /** - * @return {Promise} - * @throws {WriteError} the data cannot be written - */ - write() { - let init = this.getOption("write.init"); - if (!isObject(init)) init = {}; - if (typeof init["headers"] !== "object") { - init["headers"] = { - "Content-Type": "application/json", - }; - } - if (!init["method"]) init["method"] = "POST"; - - const obj = this.prepareServerPayload(this.get()); - init["body"] = JSON.stringify(obj); - - const callback = this.getOption("write.responseCallback"); - return fetchData.call(this, init, "write", callback); - } - - /** - * @return {RestAPI} - */ - getClone() { - return new RestAPI( - this[internalSymbol].getRealSubject()["options"].read, - this[internalSymbol].getRealSubject()["options"].write, - ); - } + /** + * + * @param {Object} [options] options contains definitions for the datasource. + */ + constructor(options) { + super(); + + if (isObject(options)) { + this.setOptions(options); + } + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/server/restapi"); + } + + /** + * @property {Object} write={} Options + * @property {Object} write.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} + * @property {string} write.init.method=POST + * @property {Object} write.init.headers Object containing any custom headers that you want to apply to the request. + * @property {string} write.responseCallback Callback function to be executed after the request has been completed. + * @property {string} write.acceptedStatus=[200,201] + * @property {string} write.url URL + * @property {Object} write.mapping the mapping is applied before writing. + * @property {String} write.mapping.transformer Transformer to select the appropriate entries + * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. + * @property {Object} write.report + * @property {String} write.report.path Path to validations + * @property {Object} write.partial + * @property {Function} write.partial.callback Callback function to be executed after the request has been completed. (obj, diffResult) => obj + * @property {Object} write.sheathing + * @property {Object} write.sheathing.object Object to be wrapped + * @property {string} write.sheathing.path Path to the data + * @property {Object} read={} Options + * @property {Object} read.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} + * @property {string} read.init.method=GET + * @property {string} read.acceptedStatus=[200] + * @property {string} read.url URL + * @property {Object} read.mapping the mapping is applied after reading. + * @property {String} read.mapping.transformer Transformer to select the appropriate entries + * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading. + */ + get defaults() { + return Object.assign({}, super.defaults, { + write: { + init: { + method: "POST", + }, + responseCallback: undefined, + acceptedStatus: [200, 201], + url: null, + mapping: { + transformer: undefined, + callbacks: [], + }, + sheathing: { + object: undefined, + path: undefined, + }, + report: { + path: undefined, + }, + + partial: { + callback: null, + }, + }, + read: { + init: { + method: "GET", + }, + responseCallback: undefined, + acceptedStatus: [200], + url: null, + mapping: { + transformer: undefined, + callbacks: [], + }, + }, + }); + } + + /** + * @return {Promise} + * @throws {Error} the options does not contain a valid json definition + * @throws {TypeError} value is not a object + * @throws {Error} the data cannot be read + */ + read() { + let init = this.getOption("read.init"); + if (!isObject(init)) init = {}; + if (!init["method"]) init["method"] = "GET"; + + let callback = this.getOption("read.responseCallback"); + if (!callback) { + callback = (obj) => { + this.set(this.transformServerPayload.call(this, obj)); + }; + } + + return fetchData.call(this, init, "read", callback); + } + + /** + * @return {Promise} + * @throws {WriteError} the data cannot be written + */ + write() { + let init = this.getOption("write.init"); + if (!isObject(init)) init = {}; + if (typeof init["headers"] !== "object") { + init["headers"] = { + "Content-Type": "application/json", + }; + } + if (!init["method"]) init["method"] = "POST"; + + const obj = this.prepareServerPayload(this.get()); + init["body"] = JSON.stringify(obj); + + const callback = this.getOption("write.responseCallback"); + return fetchData.call(this, init, "write", callback); + } + + /** + * @return {RestAPI} + */ + getClone() { + return new RestAPI( + this[internalSymbol].getRealSubject()["options"].read, + this[internalSymbol].getRealSubject()["options"].write, + ); + } } /** @@ -192,47 +191,50 @@ class RestAPI extends Server { * @returns {Promise<string>} */ function fetchData(init, key, callback) { - let response; - - return fetch(this.getOption(`${key}.url`), init) - .then((resp) => { - response = resp; - - const acceptedStatus = this.getOption(`${key}.acceptedStatus`, [200]).map(Number); - - if (acceptedStatus.indexOf(resp.status) === -1) { - throw new DataFetchError( - `the response does not contain an accepted status (actual: ${resp.status}).`, - response, - ); - } - - return resp.text(); - }) - .then((body) => { - let obj; - - try { - obj = JSON.parse(body); - - response[rawDataSymbol] = obj; - } catch (e) { - if (body.length > 100) { - body = `${body.substring(0, 97)}...`; - } - - throw new DataFetchError( - `the response does not contain a valid json (actual: ${body}).`, - response, - ); - } - - if (callback && isFunction(callback)) { - callback(obj); - } - - return response; - }).catch((e) => { - throw e; - }); + let response; + + return fetch(this.getOption(`${key}.url`), init) + .then((resp) => { + response = resp; + + const acceptedStatus = this.getOption(`${key}.acceptedStatus`, [200]).map( + Number, + ); + + if (acceptedStatus.indexOf(resp.status) === -1) { + throw new DataFetchError( + `the response does not contain an accepted status (actual: ${resp.status}).`, + response, + ); + } + + return resp.text(); + }) + .then((body) => { + let obj; + + try { + obj = JSON.parse(body); + + response[rawDataSymbol] = obj; + } catch (e) { + if (body.length > 100) { + body = `${body.substring(0, 97)}...`; + } + + throw new DataFetchError( + `the response does not contain a valid json (actual: ${body}).`, + response, + ); + } + + if (callback && isFunction(callback)) { + callback(obj); + } + + return response; + }) + .catch((e) => { + throw e; + }); } -- GitLab