diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 309eaadbf7e07c85311bb5f9d0b2014fbde2ae57..ff3298531853b964cf71c5ad3cb03f539c73691f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,40 +20,40 @@ before_script: after_script: - nix develop .#gitlab --command clean-up -#tests: -# stage: test -# tags: -# - nixos-gen3 -# script: -# - nix develop .#gitlab --command run-ci-tests -# cache: -# untracked: true -# key: -# files: -# - pnpm-lock.yaml -# paths: -# - node_modules/ -# rules: -# - if: $DEPLOY_VERSION == null -# -#web-tests: -# stage: test -# tags: -# - nixos-gen3 -# script: -# - nix develop .#gitlab --command run-ci-web-tests -# cache: -# untracked: true -# key: -# files: -# - pnpm-lock.yaml -# paths: -# - node_modules/ -# artifacts: -# paths: -# - screenshot.png -# rules: -# - if: $DEPLOY_VERSION == null +tests: + stage: test + tags: + - nixos-gen3 + script: + - nix develop .#gitlab --command run-ci-tests + cache: + untracked: true + key: + files: + - pnpm-lock.yaml + paths: + - node_modules/ + rules: + - if: $DEPLOY_VERSION == null + +web-tests: + stage: test + tags: + - nixos-gen3 + script: + - nix develop .#gitlab --command run-ci-web-tests + cache: + untracked: true + key: + files: + - pnpm-lock.yaml + paths: + - node_modules/ + artifacts: + paths: + - screenshot.png + rules: + - if: $DEPLOY_VERSION == null release: stage: release diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a9eee4b0c7b6c8e19cb34e1f0b6a5a7db8b445d..5a40741f96f79d6f4f4ef77973ff536dd9314ff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,56 +1,14 @@ # Changelog - - ## [3.65.20] - 2024-06-20 -### Bug Fixes - -- wip new pipeline -- wip new pipeline - - - -## [3.65.19] - 2024-06-20 - -### Bug Fixes - -- wip new pipeline - - - -## [3.65.18] - 2024-06-20 - -### Bug Fixes - -- wip new pipeline - - - -## [3.65.17] - 2024-06-20 - -### Bug Fixes - -- wip new pipeline - - - -## [3.65.16] - 2024-06-20 - -### Bug Fixes - -- wip new pipeline - - - -## [3.65.15] - 2024-06-20 - +- Changeover to new release process ## [3.65.3] - 2024-06-19 ### Bug Fixes -- switch nodejs_22 to nodejs_20 +- switch nodejs_22 to nodejs_20 (segmentation fault) ## [3.65.1] - 2024-06-17 diff --git a/development/config/import.mjs b/development/config/import.mjs index b057d3be4f496b77e80abc03578d7574a71eaded..80aa1a3877ab0d76e51efb7c75a4644cbc0b4b2a 100644 --- a/development/config/import.mjs +++ b/development/config/import.mjs @@ -2,7 +2,7 @@ export const projectRoot = "/home/vs/workspaces/oss/monster/monster"; export const sourcePath = "/home/vs/workspaces/oss/monster/monster/source"; export const developmentPath = "/home/vs/workspaces/oss/monster/monster/development"; export const pnpxBin = "/nix/store/77wblnm5dnmgnan3695j3mk4r7j75s5j-pnpm-8.15.5/bin/pnpx"; -export const nodeBin = "/nix/store/72m3szv59j74b12dmicmayvvlikh65qc-nodejs-22.2.0/bin/node"; +export const nodeBin = "/nix/store/6g9n96qf1yx139xklnmy3v4xhjvjgsji-nodejs-20.12.2/bin/node"; export const license = "/**" + "\n" + " * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved." + "\n" + " * Node module: @schukai/monster" + "\n" + diff --git a/nix/scripts/deploy.nix b/nix/scripts/deploy.nix index 11ca6bc333e13cddd8357ef89f940bb557d5f2bd..46dc69cde634505775747b5e5c65eb3151e1f374 100644 --- a/nix/scripts/deploy.nix +++ b/nix/scripts/deploy.nix @@ -15,8 +15,6 @@ in archive=$(ls ${monster} | grep tgz) echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc - ${pkgs'.nodejs_20}/bin/npm publish "${monster}/$archive" - if ! publishingResult=$(${pkgs'.nodejs_20}/bin/npm publish "${monster}/$archive" --no-git-checks --access public) then if [ -f .npmrc ] ; then rm .npmrc ; fi diff --git a/source/components/datatable/change-button.mjs b/source/components/datatable/change-button.mjs index f4ae679dbac44033167f805b8765efb413d39cf9..2f649b974a8b026265ff095b26ad56fed94d451b 100644 --- a/source/components/datatable/change-button.mjs +++ b/source/components/datatable/change-button.mjs @@ -20,6 +20,7 @@ import { CustomElement, registerCustomElement, } from "../../dom/customelement.mjs"; +import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; import { isString } from "../../types/is.mjs"; import { State } from "../form/types/state.mjs"; import "../form/state-button.mjs"; @@ -189,12 +190,12 @@ function initControlReferences() { const selector = this.getOption("dataset.selector"); if (isString(selector)) { - const elements = document.querySelectorAll(selector); - if (elements.length !== 1) { + + const element = findElementWithSelectorUpwards( this,selector); + if (element===null) { throw new Error("the selector must match exactly one element"); } - const element = elements[0]; if (!(element instanceof HTMLElement)) { throw new TypeError("the element must be a dataset"); } @@ -205,12 +206,11 @@ function initControlReferences() { const selector2 = this.getOption("overlay.selector"); if (isString(selector2)) { - const elements = document.querySelectorAll(selector2); - if (elements.length !== 1) { + const element = findElementWithSelectorUpwards( this,selector); + if (element===null) { throw new Error("the selector must match exactly one element"); } - const element = elements[0]; if (!(element instanceof HTMLElement)) { throw new TypeError("the element must be a overlay"); } diff --git a/source/components/datatable/dataset.mjs b/source/components/datatable/dataset.mjs index 7f795f771e18fd8ad7f46ec108740a029c17e4ba..2b233ce342a188067a84ed020e18d48d154151bf 100644 --- a/source/components/datatable/dataset.mjs +++ b/source/components/datatable/dataset.mjs @@ -12,32 +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 { 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. @@ -78,185 +79,184 @@ 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, - }, - - data: {}, - }); - - updateOptionsFromArguments.call(this, obj); - return obj; - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-dataset"; - } - - 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 = 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](); - - // initControlReferences.call(self); - initEventHandler.call(this); - - const selector = this.getOption("datasource.selector"); - - if (isString(selector)) { - const elements = document.querySelectorAll(selector); - if (elements.length !== 1) { - throw new Error("the selector must match exactly one element"); - } - - const element = elements[0]; - 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); - }), - ); - } - - /** - * @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, + }, + + data: {}, + }); + + updateOptionsFromArguments.call(this, obj); + return obj; + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-dataset"; + } + + 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 = 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](); + + // initControlReferences.call(self); + 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); + }), + ); + } + + /** + * @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)); + } + }; } /** @@ -264,17 +264,17 @@ 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; + } } /** @@ -282,8 +282,8 @@ function updateOptionsFromArguments(options) { * @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/datatable.mjs b/source/components/datatable/datatable.mjs index cb418a1a2526f561402f24f115c39275ec38e678..cb43ce25a3b1512065a1f5fe5390d4d8c0045d7c 100644 --- a/source/components/datatable/datatable.mjs +++ b/source/components/datatable/datatable.mjs @@ -511,8 +511,7 @@ function getFilterConfigKey() { * @returns {Promise} */ function getHostConfig(callback) { - const document = getDocument(); - const host = document.querySelector("monster-host"); + const host = findElementWithSelectorUpwards( this,"monster-host"); if (!(host && this.id)) { return Promise.resolve({}); @@ -608,8 +607,7 @@ function updateConfigColumnBar() { map[option.name] = option.visible; } - const document = getDocument(); - const host = document.querySelector("monster-host"); + const host = findElementWithSelectorUpwards( this,"monster-host"); if (!(host && this.id)) { return; } @@ -816,8 +814,7 @@ function storeOrderStatement(doFetch) { const statement = createOrderStatement(headers); setDataSource.call(this, { orderBy: statement }, doFetch); - const document = getDocument(); - const host = document.querySelector("monster-host"); + const host = findElementWithSelectorUpwards( this,"monster-host"); if (!(host && this.id)) { return; } diff --git a/source/components/datatable/filter.mjs b/source/components/datatable/filter.mjs index 4e2c4014a8d3e257c4fde6423051494290a32ef2..ed45310594a76230d87777d08a6b9cdc3e97509b 100644 --- a/source/components/datatable/filter.mjs +++ b/source/components/datatable/filter.mjs @@ -726,8 +726,7 @@ function initTabEvents() { } } - const document = getDocument(); - const host = document.querySelector("monster-host"); + const host = findElementWithSelectorUpwards( this,"monster-host"); if (!(host && this.id)) { return; } @@ -768,8 +767,7 @@ function updateFilterTabs() { return; } - const document = getDocument(); - const host = document.querySelector("monster-host"); + const host = findElementWithSelectorUpwards( this,"monster-host"); if (!(host && this.id)) { return; } @@ -1067,8 +1065,7 @@ function getControlValuesFromLabel(label) { * @returns {Promise<unknown>} */ function initFromConfig() { - const document = getDocument(); - const host = document.querySelector("monster-host"); + const host = findElementWithSelectorUpwards( this,"monster-host"); if (!(isInstance(host, Host) && this.id)) { return Promise.resolve(); @@ -1111,8 +1108,7 @@ function initFromConfig() { * @private */ function updateConfig() { - const document = getDocument(); - const host = document.querySelector("monster-host"); + const host = findElementWithSelectorUpwards( this,"monster-host"); if (!(host && this.id)) { return; } diff --git a/source/components/datatable/pagination.mjs b/source/components/datatable/pagination.mjs index 5ff4a9c4cd117fd09a63cca4d02b72aed7b5882a..93ce8c562cd18db9d82347a7df2691d0eca4e939 100644 --- a/source/components/datatable/pagination.mjs +++ b/source/components/datatable/pagination.mjs @@ -17,6 +17,7 @@ import { CustomElement, registerCustomElement, } from "../../dom/customelement.mjs"; +import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; import { ThemeStyleSheet } from "../stylesheet/theme.mjs"; import { ATTRIBUTE_DATASOURCE_SELECTOR } from "./constants.mjs"; import { Datasource } from "./datasource.mjs"; @@ -188,12 +189,11 @@ class Pagination extends CustomElement { const selector = this.getOption("datasource.selector", ""); if (isString(selector)) { - const elements = document.querySelectorAll(selector); - if (elements.length !== 1) { + const element = findElementWithSelectorUpwards( this,selector); + if (element===null) { throw new Error("the selector must match exactly one element"); } - - const element = elements[0]; + if (!(element instanceof Datasource)) { throw new TypeError("the element must be a datasource"); } diff --git a/source/components/datatable/save-button.mjs b/source/components/datatable/save-button.mjs index 97652648d57fc6f09ad1985cf7b487913cfc25cc..9e3322e1eddc84508dc7e3421c2cb69dfdeff7f1 100644 --- a/source/components/datatable/save-button.mjs +++ b/source/components/datatable/save-button.mjs @@ -22,6 +22,7 @@ import { attributeObserverSymbol, registerCustomElement, } from "../../dom/customelement.mjs"; +import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; import { isString, isArray } from "../../types/is.mjs"; import { Observer } from "../../types/observer.mjs"; import { TokenList } from "../../types/tokenlist.mjs"; @@ -154,12 +155,12 @@ class SaveButton extends CustomElement { const selector = this.getOption("datasource.selector"); if (isString(selector)) { - const elements = document.querySelectorAll(selector); - if (elements.length !== 1) { + + const element = findElementWithSelectorUpwards( this,selector); + if (element===null) { throw new Error("the selector must match exactly one element"); } - - const element = elements[0]; + if (!(element instanceof Datasource)) { throw new TypeError("the element must be a datasource"); } diff --git a/source/components/datatable/status.mjs b/source/components/datatable/status.mjs index 6feec9b8309a00583bcbec5532ea39a077d43518..79f617b1142782b511d66378dc3c32cb3cce8e9e 100644 --- a/source/components/datatable/status.mjs +++ b/source/components/datatable/status.mjs @@ -17,6 +17,7 @@ import { CustomElement, registerCustomElement, } from "../../dom/customelement.mjs"; +import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; import { ThemeStyleSheet } from "../stylesheet/theme.mjs"; import { Datasource } from "./datasource.mjs"; import { SpinnerStyleSheet } from "../stylesheet/spinner.mjs"; @@ -174,12 +175,12 @@ function initEventHandler() { const self = this; if (isString(selector)) { - const elements = document.querySelectorAll(selector); - if (elements.length !== 1) { + + const element = findElementWithSelectorUpwards( this,selector); + if (element===null) { throw new Error("the selector must match exactly one element"); } - - const element = elements[0]; + if (!(element instanceof Datasource)) { throw new TypeError("the element must be a datasource"); } diff --git a/source/data/datasource/dom.mjs b/source/data/datasource/dom.mjs index 28c5612a80a7519357ee35bf32eb04c878ff5205..bf7d4a607f513fec276dbae971b26daf4da13b64 100644 --- a/source/data/datasource/dom.mjs +++ b/source/data/datasource/dom.mjs @@ -12,11 +12,12 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { instanceSymbol } from "../../constants.mjs"; -import { isObject } from "../../types/is.mjs"; -import { Datasource } from "../datasource.mjs"; +import {instanceSymbol} from "../../constants.mjs"; +import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; +import {isObject} from "../../types/is.mjs"; +import {Datasource} from "../datasource.mjs"; -export { DomStorage }; +export {DomStorage}; /** * The DomStorage is a class that stores data in memory. @@ -26,93 +27,93 @@ export { DomStorage }; * @memberOf Monster.Data.Datasource */ class DomStorage extends Datasource { - /** - * @param {Object} [options] options contains definitions for the datasource. - */ - constructor(options) { - super(); + /** + * @param {Object} [options] options contains definitions for the datasource. + */ + constructor(options) { + super(); - if (isObject(options)) { - this.setOptions(options); - } - } + if (isObject(options)) { + this.setOptions(options); + } + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/storage/dom-storage"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/storage/dom-storage"); + } - /** - * @property {Object} defaults - * @property {Object} defaults.read - * @property {string} defaults.read.selector - * @property {Object} defaults.write - * @property {string} defaults.write.selector - */ - get defaults() { - return Object.assign({}, super.defaults, { - read: { - selector: undefined, - }, - write: { - selector: undefined, - }, - }); - } + /** + * @property {Object} defaults + * @property {Object} defaults.read + * @property {string} defaults.read.selector + * @property {Object} defaults.write + * @property {string} defaults.write.selector + */ + get defaults() { + return Object.assign({}, super.defaults, { + read: { + selector: undefined, + }, + write: { + selector: undefined, + }, + }); + } - /** - * @return {Promise} - * @throws {Error} The read selector is not defined - * @throws {Error} There are no storage element - */ - read() { - const selector = this.getOption("read.selector", undefined); - if (!selector) { - throw new Error("The read selector is not defined"); - } + /** + * @return {Promise} + * @throws {Error} The read selector is not defined + * @throws {Error} There are no storage element + */ + read() { + const selector = this.getOption("read.selector", undefined); + if (!selector) { + throw new Error("The read selector is not defined"); + } - const storage = document.querySelector(selector); - if (!storage) { - throw new Error("There are no storage element"); - } + const storage = findElementWithSelectorUpwards(this, selector); + if (!storage) { + throw new Error("There is no storage element"); + } - return new Promise((resolve, reject) => { - try { - const data = JSON.parse(storage.innerHTML); - this.set(data); - resolve(data); - } catch (e) { - reject(e); - } - }); - } + return new Promise((resolve, reject) => { + try { + const data = JSON.parse(storage.innerHTML); + this.set(data); + resolve(data); + } catch (e) { + reject(e); + } + }); + } - /** - * @return {Promise} - * @throws {Error} The write selector is not defined - * @throws {Error} There are no storage element - */ - write() { - const selector = this.getOption("write.selector"); - if (!selector) { - throw new Error("The write selector is not defined"); - } + /** + * @return {Promise} + * @throws {Error} The write selector is not defined + * @throws {Error} There are no storage element + */ + write() { + const selector = this.getOption("write.selector"); + if (!selector) { + throw new Error("The option write.selector is not defined"); + } - const storage = document.querySelector(selector); - if (!storage) { - throw new Error("There are no storage element"); - } + const storage = findElementWithSelectorUpwards(this, selector); + if (!storage) { + throw new Error("There is no storage element"); + } - return new Promise((resolve, reject) => { - try { - storage.innerHTML = JSON.stringify(this.get()); - resolve(storage); - } catch (e) { - reject(e); - } - }); - } + return new Promise((resolve, reject) => { + try { + storage.innerHTML = JSON.stringify(this.get()); + resolve(storage); + } catch (e) { + reject(e); + } + }); + } } diff --git a/source/dom/resource.mjs b/source/dom/resource.mjs index 86bb08823a59036a626d2b648611f7cb1d38e86e..ea78064ab7868430bb7551a5cf86be1e1377d008 100644 --- a/source/dom/resource.mjs +++ b/source/dom/resource.mjs @@ -242,7 +242,6 @@ function addEvents() { this[referenceSymbol].removeEventListener("error", onError); this[referenceSymbol].removeEventListener("load", onLoad); this[internalStateSymbol].getSubject()["loaded"] = true; - return; }; this[referenceSymbol].addEventListener("load", onLoad, false); diff --git a/source/dom/resource/data.mjs b/source/dom/resource/data.mjs index 7adcbaf1591d483d911e0e6384c38ec14682b198..0eaff437aba2dcdd60830f5ad864d54cf1c448d5 100644 --- a/source/dom/resource/data.mjs +++ b/source/dom/resource/data.mjs @@ -12,26 +12,28 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { internalStateSymbol } from "../../constants.mjs"; -import { extend } from "../../data/extend.mjs"; -import { getGlobalFunction } from "../../types/global.mjs"; +import {internalStateSymbol} from "../../constants.mjs"; +import {extend} from "../../data/extend.mjs"; +import {getGlobalFunction} from "../../types/global.mjs"; +import {addAttributeToken} from "../attributes.mjs"; import { - ATTRIBUTE_CLASS, - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_ID, - ATTRIBUTE_SRC, - ATTRIBUTE_TITLE, - ATTRIBUTE_TYPE, - TAG_SCRIPT, + ATTRIBUTE_CLASS, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_ID, + ATTRIBUTE_SRC, + ATTRIBUTE_TITLE, + ATTRIBUTE_TYPE, + TAG_SCRIPT, } from "../constants.mjs"; import { - KEY_DOCUMENT, - KEY_QUERY, - referenceSymbol, - Resource, + KEY_DOCUMENT, + KEY_QUERY, + referenceSymbol, + Resource, } from "../resource.mjs"; -import { instanceSymbol } from "../../constants.mjs"; -export { Data }; +import {instanceSymbol} from "../../constants.mjs"; + +export {Data}; /** * This class is used by the resource manager to embed data. @@ -43,58 +45,59 @@ export { Data }; * @summary A Data Resource class */ class Data extends Resource { - /** - * @property {string} mode=cors https://developer.mozilla.org/en-US/docs/Web/API/fetch - * @property {string} credentials=same-origin https://developer.mozilla.org/en-US/docs/Web/API/fetch - * @property {string} type=application/json {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type} - */ - get defaults() { - return extend({}, super.defaults, { - mode: "cors", - credentials: "same-origin", - type: "application/json", - }); - } - - /** - * - * @return {Monster.DOM.Resource.Data} - */ - create() { - createElement.call(this); - return this; - } - - /** - * This method appends the HTMLElement to the specified document - * - * throws {Error} target not found - * @return {Monster.DOM.Resource} - */ - connect() { - if (!(this[referenceSymbol] instanceof HTMLElement)) { - this.create(); - } - - appendToDocument.call(this); - return this; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/resource/data"); - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC; - } + /** + * @property {string} mode=cors https://developer.mozilla.org/en-US/docs/Web/API/fetch + * @property {string} credentials=same-origin https://developer.mozilla.org/en-US/docs/Web/API/fetch + * @property {string} type=application/json {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type} + */ + get defaults() { + return extend({}, super.defaults, { + mode: "cors", + credentials: "same-origin", + type: "application/json", + }); + } + + /** + * + * @return {Monster.DOM.Resource.Data} + */ + create() { + createElement.call(this); + return this; + } + + /** + * This method appends the HTMLElement to the specified document + * + * throws {Error} target not found + * @return {Monster.DOM.Resource} + */ + connect() { + const self = this; + if (!(this[referenceSymbol] instanceof HTMLElement)) { + this.create(); + } + + appendToDocument.call(this); + return this; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/resource/data"); + } + + /** + * @return {string} + */ + static getURLAttribute() { + return ATTRIBUTE_SRC; + } } /** @@ -102,21 +105,21 @@ class Data extends Resource { * @return {Monster.DOM.Resource.Data} */ function createElement() { - const document = this.getOption(KEY_DOCUMENT); - this[referenceSymbol] = document.createElement(TAG_SCRIPT); - - for (const key of [ - ATTRIBUTE_TYPE, - ATTRIBUTE_ID, - ATTRIBUTE_CLASS, - ATTRIBUTE_TITLE, - ]) { - if (this.getOption(key) !== undefined) { - this[referenceSymbol][key] = this.getOption(key); - } - } - - return this; + const document = this.getOption(KEY_DOCUMENT); + this[referenceSymbol] = document.createElement(TAG_SCRIPT); + + for (const key of [ + ATTRIBUTE_TYPE, + ATTRIBUTE_ID, + ATTRIBUTE_CLASS, + ATTRIBUTE_TITLE, + ]) { + if (this.getOption(key) !== undefined) { + this[referenceSymbol][key] = this.getOption(key); + } + } + + return this; } /** @@ -125,41 +128,41 @@ function createElement() { * throws {Error} target not found */ function appendToDocument() { - const targetNode = document.querySelector(this.getOption(KEY_QUERY, "head")); - if (!(targetNode instanceof HTMLElement)) { - throw new Error("target not found"); - } - - targetNode.appendChild(this[referenceSymbol]); - - getGlobalFunction("fetch")(this.getOption(ATTRIBUTE_SRC), { - method: "GET", // *GET, POST, PUT, DELETE, etc. - mode: this.getOption("mode", "cors"), // no-cors, *cors, same-origin - cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached - credentials: this.getOption("credentials", "same-origin"), // include, *same-origin, omit - headers: { - Accept: this.getOption("type", "application/json"), - }, - redirect: "follow", // manual, *follow, error - referrerPolicy: "no-referrer", // no-referrer, - }) - .then((response) => { - return response.text(); - }) - .then((text) => { - const textNode = document.createTextNode(text); - this[referenceSymbol].appendChild(textNode); - - this[internalStateSymbol].getSubject()["loaded"] = true; - }) - .catch((e) => { - this[internalStateSymbol].setSubject({ - loaded: true, - error: e.toString(), - }); - - targetNode.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString()); - }); - - return this; + const targetNode = document.querySelector(this.getOption(KEY_QUERY, "head")); + if (!(targetNode instanceof HTMLElement)) { + throw new Error("target not found"); + } + + targetNode.appendChild(this[referenceSymbol]); + + getGlobalFunction("fetch")(this.getOption(ATTRIBUTE_SRC), { + method: "GET", // *GET, POST, PUT, DELETE, etc. + mode: this.getOption("mode", "cors"), // no-cors, *cors, same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + credentials: this.getOption("credentials", "same-origin"), // include, *same-origin, omit + headers: { + Accept: this.getOption("type", "application/json"), + }, + redirect: "follow", // manual, *follow, error + referrerPolicy: "no-referrer", // no-referrer, + }) + .then((response) => { + return response.text(); + }) + .then((text) => { + const textNode = document.createTextNode(text); + this[referenceSymbol].appendChild(textNode); + + this[internalStateSymbol].getSubject()["loaded"] = true; + }) + .catch((e) => { + this[internalStateSymbol].setSubject({ + loaded: true, + error: e.toString(), + }); + + targetNode.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString()); + }); + + return this; }