diff --git a/development/issues/closed/217.html b/development/issues/closed/217.html index 8b88b8bdb22e407aa8b4668533d1ccdac4f429ad..a663753d2def72cfb6630a8254e46508f7b59130 100644 --- a/development/issues/closed/217.html +++ b/development/issues/closed/217.html @@ -1,71 +1,83 @@ - <!DOCTYPE html> - <html lang="en"> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>only try to transfer the part that has been changed #217</title> - <script src="./217.mjs" type="module"></script> - </head> - <body> - <h1>only try to transfer the part that has been changed #217</h1> - <p></p> - <ul> - <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/217">Issue #217</a></li> - <li><a href="/">Back to overview</a></li> - </ul> - <main> - - <monster-datasource-rest id="ds210" - data-monster-option-read-url="/issue-217.json" - data-monster-option-write-url="/issue-217" - data-monster-option-write-acceptedstatus="400::200" - data-monster-option-features-autoinit="true"> - </monster-datasource-rest> - - <monster-dataset data-monster-option-index="0" - data-monster-option-datasource-selector="#ds210" - data-monster-option-mapping-data=""> - ID: - <div data-monster-replace="path:data.id"></div> - <div data-monster-replace="path:data.name"></div> - </monster-dataset> - - - <monster-form data-monster-option-datasource-selector="#ds210" - data-monster-option-mapping-data="" - data-monster-option-features-mutationobserver="true" - > - - <monster-field-set data-monster-option-labels-title="my title"> - - <label for="id">field id</label><input id="id" type="number" - data-monster-attributes="value path:data.id" - data-monster-bind="path:data.id"> - - <label for="field1">field id</label><input id="field1" - data-monster-attributes="value path:data.field1" - data-monster-bind="path:data.field1"> - - <label for="field2">field id</label><input id="field2" - data-monster-attributes="value path:data.field2" - data-monster-bind="path:data.field2"> - - <label for="field3">field id</label><input id="field3" - data-monster-attributes="value path:data.field3" - data-monster-bind="path:data.field3"> - - - </monster-field-set> - <monster-field-set data-monster-option-labels-title=""> - - <monster-datasource-save-button data-monster-option-datasource-selector="#ds210">ok - </monster-datasource-save-button> - - </monster-field-set> - - - </monster-form> - - </main> - </body> - </html> +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>only try to transfer the part that has been changed #217</title> + <script src="./217.mjs" type="module"></script> +</head> +<body> +<h1>only try to transfer the part that has been changed #217</h1> +<p></p> +<ul> + <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/217">Issue #217</a></li> + <li><a href="/">Back to overview</a></li> +</ul> +<main> + + <monster-datasource-rest id="ds210" + data-monster-option-read-url="/issue-217.json" + data-monster-option-write-url="/issue-217" + data-monster-option-write-acceptedstatus="400::200" + data-monster-option-features-autoinit="true"> + </monster-datasource-rest> + + <monster-dataset data-monster-option-index="0" + data-monster-option-datasource-selector="#ds210" + data-monster-option-mapping-data=""> + ID: + <div data-monster-replace="path:data.id"></div> + <div data-monster-replace="path:data.name"></div> + <div data-monster-replace="path:data.field4"></div> + </monster-dataset> + + + <monster-form data-monster-option-datasource-selector="#ds210" + data-monster-option-mapping-data="" + data-monster-option-features-mutationobserver="true" + > + + <monster-field-set data-monster-option-labels-title="my title"> + + <label for="id">field id</label><input id="id" type="number" + data-monster-attributes="value path:data.id" + data-monster-bind="path:data.id"> + + <label for="field1">field id</label><input id="field1" + data-monster-attributes="value path:data.field1" + data-monster-bind="path:data.field1"> + + <label for="field2">field id</label><input id="field2" + data-monster-attributes="value path:data.field2" + data-monster-bind="path:data.field2"> + + <label for="field3">field id</label><input id="field3" + data-monster-attributes="value path:data.field3" + data-monster-bind="path:data.field3"> + + <label for="field3">field id</label> + <monster-select id="field4" + data-monster-option-type="checkbox" + data-monster-attributes="value path:data.field4" + data-monster-bind="path:data.field4"> + <div data-monster-value="a">a</div> + <div data-monster-value="b">b</div> + <div data-monster-value="c">c</div> + <div data-monster-value="d">d</div> + </monster-select> + + + </monster-field-set> + <monster-field-set data-monster-option-labels-title=""> + + <monster-datasource-save-button data-monster-option-datasource-selector="#ds210">ok + </monster-datasource-save-button> + + </monster-field-set> + + + </monster-form> + +</main> +</body> +</html> diff --git a/development/issues/closed/217.mjs b/development/issues/closed/217.mjs index c9737abad038f65c70bbe0accad1b6356f40cf12..eeb11897b9f134d0ab779683a32442139b5529b1 100644 --- a/development/issues/closed/217.mjs +++ b/development/issues/closed/217.mjs @@ -22,6 +22,12 @@ import {domReady} from "../../../source/dom/ready.mjs"; domReady.then(() => { + const element = document.getElementById("field4"); + console.log(element); + setTimeout(() => { + element.value = "x"; + },100); + element.value = "x"; }); diff --git a/development/issues/open/289.html b/development/issues/open/289.html new file mode 100644 index 0000000000000000000000000000000000000000..ee1b3888dc46b51bfc634b73b634150b98d4247b --- /dev/null +++ b/development/issues/open/289.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>function for tags in datatable #289</title> + <script src="./289.mjs" type="module"></script> +</head> +<body> + <h1>function for tags in datatable #289</h1> + <p></p> + <ul> + <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/289">Issue #289</a></li> + <li><a href="/">Back to overview</a></li> + </ul> + <main> + + <main> + + <monster-host><monster-config-manager></monster-config-manager></monster-host> + <monster-notify data-monster-option-orientation="bottom right"></monster-notify> + <monster-monitor-attribute-errors + data-monster-option-features-notifyUser="false" + ></monster-monitor-attribute-errors> + + + <h2>Control with DOM datasource</h2> + + <monster-datasource-rest id="ds277" + data-monster-option-features-autoInit="true" + data-monster-option-write-url="/issue-274.json" + data-monster-option-read-url="/issue-274.json?limit=5&page=${page}&q=${query}&order=${order}" + data-monster-option-filter-id="ds277-filter" + data-monster-option-features-filter="true" + ></monster-datasource-rest> + + + <monster-datatable data-monster-datasource-selector="#ds277" id="dt277" + data-monster-option-datasource-orderdelimiter="::"> + + <monster-collapse id="filter-collapse" data-monster-role="filter-collapse"> + <div class="flex"> + + <monster-tabs style="width: 100%" + data-monster-option-features-opendelay="500" + data-monster-option-classes-navigation="monster-theme-background-inherit" + data-monster-option-classes-button="monster-theme-background-inherit" id="filtertabs"> + <div data-monster-button-label="Filter" data-monster-state="active" class="active"> + + <monster-datatable-filter id="ds277-filter" slot="filter" + data-monster-option-storedconfig-selector="#filtertabs"> + + <label data-monster-label="ID" id="id" + data-monster-template="${value | call:range:id}"> + ID + <monster-filter-range></monster-filter-range> + </label> + + <label data-monster-label="Fullname" id="fullname" + data-monster-template="${value}"> + ID + <monster-filter-input></monster-filter-input> + </label> + + </monster-datatable-filter> + </div> + </monster-tabs> + </div> + </monster-collapse> + + <div slot="bar" class="monster-button-group"> + + + <monster-embedded-pagination data-monster-datasource-selector="#ds277"></monster-embedded-pagination> + <monster-datasource-status + data-monster-option-datasource-selector="#ds277"></monster-datasource-status> + + <monster-datatable-filter-button data-monster-reference="#filter-collapse" + data-monster-role="filter-button">Filter + </monster-datatable-filter-button> + </div> + + <template id="dt277-row"> + <div data-monster-order-template="${field}=${direction}" data-monster-sortable="id" data-monster-grid-template="3rem" data-monster-mode="fixed" data-monster-head="id" data-monster-replace="path:dt277-row.id"></div> + <div data-monster-grid-template="2rem" data-monster-mode="fixed" data-monster-features="select"></div> + <div data-monster-head="username" data-monster-replace="path:dt277-row.username"></div> + <div data-monster-head="email" data-monster-replace="path:dt277-row.email"></div> + <div data-monster-sortable="full_name" data-monster-head="full_name" data-monster-replace="path:dt277-row.full_name"></div> + <div data-monster-head="age" data-monster-replace="path:dt277-row.age"></div> + <div data-monster-head="country" data-monster-replace="path:dt277-row.country"></div> + <div data-monster-head="registered_date" data-monster-replace="path:dt277-row.registered_date"></div> + <div data-monster-head="status" data-monster-replace="path:dt277-row.status | call:badge-column"></div> + </template> + + + </monster-datatable> + </main> + + </main> +</body> +</html> diff --git a/development/issues/open/289.mjs b/development/issues/open/289.mjs new file mode 100644 index 0000000000000000000000000000000000000000..b9c9a142c481979ed7dd9bbb361b1934c66c82f9 --- /dev/null +++ b/development/issues/open/289.mjs @@ -0,0 +1,54 @@ +/** +* @file development/issues/open/289.mjs +* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/289 +* @description function for tags in datatable +* @issue 289 +*/ + +import "../../../source/components/style/property.pcss"; +import "../../../source/components/style/link.pcss"; +import "../../../source/components/style/color.pcss"; +import "../../../source/components/style/theme.pcss"; +import "../../../source/components/style/normalize.pcss"; +import "../../../source/components/style/typography.pcss"; +import "../../../source/components/datatable/datasource/rest.mjs"; +import "../../../source/components/datatable/datasource/dom.mjs"; +import "../../../source/components/datatable/dataset.mjs"; +import "../../../source/components/datatable/status.mjs"; +import "../../../source/components/datatable/change-button.mjs"; +import "../../../source/components/datatable/save-button.mjs"; +import "../../../source/components/datatable/pagination.mjs"; +import "../../../source/components/datatable/embedded-pagination.mjs"; +import "../../../source/components/datatable/filter.mjs"; +import "../../../source/components/form/button.mjs"; +import "../../../source/components/form/form.mjs"; +import "../../../source/components/form/field-set.mjs"; +import "../../../source/components/form/button-bar.mjs"; +import "../../../source/components/form/toggle-switch.mjs"; +import "../../../source/components/form/context-help.mjs"; +import "../../../source/components/form/context-error.mjs"; +import "../../../source/components/form/input-group.mjs"; +import "../../../source/components/datatable/filter/input.mjs"; +import "../../../source/components/datatable/filter/date-range.mjs"; +import "../../../source/components/datatable/filter/range.mjs"; +import "../../../source/components/layout/tabs.mjs"; +import "../../../source/components/datatable/datatable.mjs"; +import "../../../source/components/notify/monitor-attribute-errors.mjs"; + +//const actionButton = document.getElementById("action-button-1") +const datatable = document.getElementById("dt277"); + +const eventHandler = function () { + const selected = datatable.getSelectedRows(); + + // if (selected.length === 0) { + // actionButton.style.visibility = "hidden"; + // } else { + // actionButton.style.visibility = "visible"; + // } + + console.log("issue 227 selected: "+selected.join(", ")); +} + +datatable.addEventListener("monster-datatable-selection-changed", eventHandler); + diff --git a/development/mock/issue-217.js b/development/mock/issue-217.js index d3af360ed74fe4c3da01186f59eeb8ce37996272..db4c8ea0b18dea4b3a6a5432f8fb665fd9d9f466 100644 --- a/development/mock/issue-217.js +++ b/development/mock/issue-217.js @@ -4,13 +4,15 @@ const json = "id": 1000, "field1": "dataset 1, value field 1", "field2": "dataset 1, value field 2", - "field3": "dataset 1, value field 3" + "field3": "dataset 1, value field 3", + "field4": "a,c,d" }, "1": { "id": 1001, "field1": "dataset 2, value field 1", "field2": "dataset 2, value field 2", - "field3": "dataset 2, value field 3" + "field3": "dataset 2, value field 3", + "field4": "a,b" } }`; diff --git a/development/templates/vite.config.mjs b/development/templates/vite.config.mjs index 67f59bf675f621f50107c701bb39f0e6f9f536c2..6ffc14a9a58d088e8a46f8874a7582d40711daec 100644 --- a/development/templates/vite.config.mjs +++ b/development/templates/vite.config.mjs @@ -88,7 +88,7 @@ export default defineConfig({ }, server: { - port: 8443, + port: 8440, host: "localhost.alvine.dev", https: { key: "${LOCALHOST_CERTS_DIR}/localhost.alvine.dev.key", diff --git a/source/components/accessibility/locale-picker.mjs b/source/components/accessibility/locale-picker.mjs index 1ee50490aed4481d80c730632f7db6d1ae582b14..38e67fe223741f8d4d898808a253e05a7dbc86c9 100644 --- a/source/components/accessibility/locale-picker.mjs +++ b/source/components/accessibility/locale-picker.mjs @@ -75,7 +75,7 @@ const detectedLanguagesSymbol = Symbol("detectedLanguages"); * @example /examples/components/accessibility/locale-picker-simple Simple example * @example /examples/components/accessibility/locale-picker-reset Reset Selection * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/276.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/276.html * * @since 3.97.0 * @copyright schukai GmbH diff --git a/source/components/host/style/viewer.pcss b/source/components/content/style/viewer.pcss similarity index 100% rename from source/components/host/style/viewer.pcss rename to source/components/content/style/viewer.pcss diff --git a/source/components/host/stylesheet/viewer.mjs b/source/components/content/stylesheet/viewer.mjs similarity index 100% rename from source/components/host/stylesheet/viewer.mjs rename to source/components/content/stylesheet/viewer.mjs diff --git a/source/components/content/viewer.mjs b/source/components/content/viewer.mjs new file mode 100644 index 0000000000000000000000000000000000000000..917d558ab6b65f603a9a828ae53bcce77ba53749 --- /dev/null +++ b/source/components/content/viewer.mjs @@ -0,0 +1,289 @@ +/** + * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. + * Node module: @schukai/monster + * + * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). + * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html + * + * For those who do not wish to adhere to the AGPLv3, a commercial license is available. + * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. + * For more information about purchasing a commercial license, please contact schukai GmbH. + * + * SPDX-License-Identifier: AGPL-3.0 + */ + +import { + assembleMethodSymbol, + CustomElement, + registerCustomElement, +} from "../../dom/customelement.mjs"; +import "../notify/notify.mjs"; +import { ViewerStyleSheet } from "./stylesheet/viewer.mjs"; +import { instanceSymbol } from "../../constants.mjs"; +import { isString } from "../../types/is.mjs"; +import { getGlobal } from "../../types/global.mjs"; + +export { Viewer }; + +/** + * @private + * @type {symbol} + */ +const viewerElementSymbol = Symbol("viewerElement"); + +/** + * The Viewer component is used to show a PDF, HTML or Image. + * + * @fragments /fragments/components/content/viewer + * + * @example /examples/components/content/pdf-viewer with a PDF + * @example /examples/components/content/image-viewer with an image + * @example /examples/components/content/html-viewer with HTML content + * + * @copyright schukai GmbH + * @summary A simple viewer component for PDF, HTML and images. + */ +class Viewer extends CustomElement { + /** + * This method is called by the `instanceof` operator. + * @return {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/content/viewer@@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} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {Object} classes Css classes + * @property {Object} features Feature definitions + */ + get defaults() { + return Object.assign({}, super.defaults, { + templates: { + main: getTemplate(), + }, + content: "<slot></slot>", + classes: { + viewer: "", + }, + features: {}, + }); + } + + /** + * + * @param {string} content + * @returns {Viewer} + */ + setContent(content) { + this.setOption("content", content); + return this; + } + + /** + * + * @param {Blob|URL|string} data + * @param {boolean} navigation + * @param {boolean} toolbar + * @param {boolean} scrollbar + */ + setPDF(data, navigation = true, toolbar = true, scrollbar = false) { + const hashes = + "#toolbar=" + + (toolbar ? "1" : "0") + + "&navpanes=" + + (navigation ? "1" : "0") + + "&scrollbar=" + + (scrollbar ? "1" : "0"); + + let pdfContent = ""; + if (isBlob(data)) { + pdfContent = URL.createObjectURL(data); + pdfContent += hashes; + } else if (isURL(data)) { + pdfContent = data; + // check if the url already contains the hashes + if (pdfContent.indexOf("#") === -1) { + pdfContent += hashes; + } + } else if (isString(data)) { + //URL.createObjectURL(data); + const blobObj = new Blob([atob(data)], { type: "application/pdf" }); + const url = window.URL.createObjectURL(blobObj); + + pdfContent = data; + } else { + throw new Error("Blob or URL expected"); + } + + const html = + '<object data="' + + pdfContent + + '" width="100%" height="100%" type="application/pdf"></object>'; + this.setContent(html); + } + + /** + * + * @param {Blob|URL|string} data + */ + setImage(data) { + if (isBlob(data)) { + data = URL.createObjectURL(data); + } else if (isURL(data)) { + // nothing to do + } else if (isString(data)) { + // nothing to do + } else { + throw new Error("Blob or URL expected"); + } + + const html = '<img src="' + data + '" alt="image" />'; + this.setContent(html); + } + + /** + * + * if the data is a string, it is interpreted as HTML. + * if the data is an url, the HTML is loaded from the url and set as content. + * if the data is an HTMLElement, the outerHTML is used as content. + * + * @param {HTMLElement|URL|string|Blob} data + */ + setHTML(data) { + if (data instanceof Blob) { + blobToText(data) + .then((html) => { + this.setHTML(html); + }) + .catch((error) => { + throw new Error(error); + }); + + return; + } else if (data instanceof HTMLElement) { + data = data.outerHTML; + } else if (isString(data)) { + // nothing to do + } else if (isURL(data)) { + // fetch element + getGlobal() + .fetch(data) + .then((response) => { + return response.text(); + }) + .then((html) => { + this.setHTML(html); + }) + .catch((error) => { + throw new Error(error); + }); + } else { + throw new Error("HTMLElement or string expected"); + } + + this.setContent(data); + } + + /** + * + * @return {Viewer} + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + + initControlReferences.call(this); + initEventHandler.call(this); + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-viewer"; + } + + /** + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [ViewerStyleSheet]; + } +} + +/** + * @private + * @param variable + * @return {boolean} + */ +function isURL(variable) { + try { + new URL(variable); + return true; + } catch (error) { + return false; + } +} + +/** + * @private + * @param variable + * @return {boolean} + */ +function isBlob(variable) { + return variable instanceof Blob; +} + +/** + * @private + * @param blob + * @return {Promise<unknown>} + */ +function blobToText(blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsText(blob); + }); +} + +/** + * @private + * @return {Select} + * @throws {Error} no shadow-root is defined + */ +function initControlReferences() { + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } + + this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer"); +} + +/** + * @private + */ +function initEventHandler() { + return this; +} + +/** + * @private + * @return {string} + */ +function getTemplate() { + // language=HTML + return ` + <div id="viewer" data-monster-role="viewer" part="viewer" data-monster-replace="path:content" data-monster-attributes="class path:classes.viewer"> + </div>`; +} + +registerCustomElement(Viewer); diff --git a/source/components/datatable/change-button.mjs b/source/components/datatable/change-button.mjs index 4fc97a164fadd67a69ae347e939694a07b8facce..246692002da5fa3e15810fbc30bebb0da42510b0 100644 --- a/source/components/datatable/change-button.mjs +++ b/source/components/datatable/change-button.mjs @@ -53,7 +53,7 @@ const overlayLinkedElementSymbol = Symbol("overlayLinkedElement"); * * @example /examples/components/datatable/change-button-simple Simple change button * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/274.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/274.html * * @copyright schukai GmbH * @summary The Status component is used to show the current status of a datasource. diff --git a/source/components/datatable/dataset.mjs b/source/components/datatable/dataset.mjs index e534c59b08c86d4a835d35f1acfd1d15000c901c..c0409b689b0e91c9ae574e512746dbe7b287961b 100644 --- a/source/components/datatable/dataset.mjs +++ b/source/components/datatable/dataset.mjs @@ -50,7 +50,7 @@ export { DataSet }; * @example /examples/components/datatable/dataset-dom Dom dataset * @example /examples/components/datatable/dataset-rest Rest dataset * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html * * @copyright schukai GmbH * @summary A dataset component that can be used to show the data of a data source diff --git a/source/components/datatable/datasource.mjs b/source/components/datatable/datasource.mjs index 32990568459893708d2683b90e2c0535c537191d..0688de399b84658d7be3bc9fc31e42cf5e7a24e0 100644 --- a/source/components/datatable/datasource.mjs +++ b/source/components/datatable/datasource.mjs @@ -36,7 +36,7 @@ const dataSourceSymbol = Symbol.for( * * @example /examples/components/datatable/datasource * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html * * @copyright schukai GmbH * @summary A generic datasource diff --git a/source/components/datatable/datasource/rest.mjs b/source/components/datatable/datasource/rest.mjs index 6d9b124cd0cddce3ec62f77d005593b2287431c4..8e1644ecde73b870209e0f3e2e92e8a8fd584375 100644 --- a/source/components/datatable/datasource/rest.mjs +++ b/source/components/datatable/datasource/rest.mjs @@ -70,7 +70,7 @@ const filterObserverSymbol = Symbol("filterObserver"); * @example /examples/components/datatable/datasource-rest-auto-init Auto init * @example /examples/components/datatable/datasource-rest-do-fetch Rest datasource with fetch * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html * * @copyright schukai GmbH * @summary A rest api datasource for the datatable or other components diff --git a/source/components/datatable/datatable.mjs b/source/components/datatable/datatable.mjs index 8b3f2eaa77866e6b5bc7228b160e9bc5434bddac..745a907a2984232d35cbc83701efe97b81c653fb 100644 --- a/source/components/datatable/datatable.mjs +++ b/source/components/datatable/datatable.mjs @@ -12,79 +12,78 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { Datasource } from "./datasource.mjs"; +import {Datasource} from "./datasource.mjs"; import { - assembleMethodSymbol, - CustomElement, - registerCustomElement, - getSlottedElements, + assembleMethodSymbol, + CustomElement, + registerCustomElement, + getSlottedElements, updaterTransformerMethodsSymbol, } from "../../dom/customelement.mjs"; import { - findTargetElementFromEvent, - fireCustomEvent, + findTargetElementFromEvent, + fireCustomEvent, } from "../../dom/events.mjs"; -import { clone } from "../../util/clone.mjs"; +import {clone} from "../../util/clone.mjs"; import { - isString, - isFunction, - isInstance, - isObject, - isArray, + isString, + isFunction, + isInstance, + isObject, + isArray, } from "../../types/is.mjs"; import { - validateArray, - validateInteger, - validateObject, + validateArray, + validateInteger, + validateObject, } from "../../types/validate.mjs"; -import { Observer } from "../../types/observer.mjs"; +import {Observer} from "../../types/observer.mjs"; import { - ATTRIBUTE_DATATABLE_HEAD, - ATTRIBUTE_DATATABLE_GRID_TEMPLATE, - ATTRIBUTE_DATASOURCE_SELECTOR, - ATTRIBUTE_DATATABLE_ALIGN, - ATTRIBUTE_DATATABLE_SORTABLE, - ATTRIBUTE_DATATABLE_MODE, - ATTRIBUTE_DATATABLE_INDEX, - ATTRIBUTE_DATATABLE_MODE_HIDDEN, - ATTRIBUTE_DATATABLE_FEATURES, - ATTRIBUTE_DATATABLE_MODE_VISIBLE, - ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT, - ATTRIBUTE_DATATABLE_MODE_FIXED, + ATTRIBUTE_DATATABLE_HEAD, + ATTRIBUTE_DATATABLE_GRID_TEMPLATE, + ATTRIBUTE_DATASOURCE_SELECTOR, + ATTRIBUTE_DATATABLE_ALIGN, + ATTRIBUTE_DATATABLE_SORTABLE, + ATTRIBUTE_DATATABLE_MODE, + ATTRIBUTE_DATATABLE_INDEX, + ATTRIBUTE_DATATABLE_MODE_HIDDEN, + ATTRIBUTE_DATATABLE_FEATURES, + ATTRIBUTE_DATATABLE_MODE_VISIBLE, + ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT, + ATTRIBUTE_DATATABLE_MODE_FIXED, } from "./constants.mjs"; -import { instanceSymbol } from "../../constants.mjs"; +import {instanceSymbol} from "../../constants.mjs"; import { - Header, - createOrderStatement, - DIRECTION_ASC, - DIRECTION_DESC, - DIRECTION_NONE, + Header, + createOrderStatement, + DIRECTION_ASC, + DIRECTION_DESC, + DIRECTION_NONE, } from "./datatable/header.mjs"; -import { DatatableStyleSheet } from "./stylesheet/datatable.mjs"; +import {DatatableStyleSheet} from "./stylesheet/datatable.mjs"; import { - handleDataSourceChanges, - datasourceLinkedElementSymbol, + handleDataSourceChanges, + datasourceLinkedElementSymbol, } from "./util.mjs"; import "./columnbar.mjs"; import "./filter-button.mjs"; import { - findElementWithSelectorUpwards, - getDocument, - getWindow, + findElementWithSelectorUpwards, + getWindow, } from "../../dom/util.mjs"; -import { getDocumentTranslations } from "../../i18n/translations.mjs"; +import {getDocumentTranslations} from "../../i18n/translations.mjs"; import "../state/state.mjs"; import "../host/collapse.mjs"; -import { generateUniqueConfigKey } from "../host/util.mjs"; +import {generateUniqueConfigKey} from "../host/util.mjs"; import "./datasource/dom.mjs"; import "./datasource/rest.mjs"; import "../form/context-help.mjs"; -import { getLocaleOfDocument } from "../../dom/locale.mjs"; -import { addErrorAttribute } from "../../dom/error.mjs"; +import {getLocaleOfDocument} from "../../dom/locale.mjs"; +import {addErrorAttribute} from "../../dom/error.mjs"; -export { DataTable }; +export {DataTable}; /** * @private @@ -139,7 +138,8 @@ const resizeObserverSymbol = Symbol("resizeObserver"); * @example /examples/components/datatable/order-by Sort data * @example /examples/components/datatable/select-rows Select rows * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/277.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/277.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/289.html * * @copyright schukai GmbH * @summary A beautiful and highly customizable data table. It can be used to display data from a data source. @@ -153,434 +153,434 @@ const resizeObserverSymbol = Symbol("resizeObserver"); * @fires monster-datatable-selection-changed **/ class DataTable extends CustomElement { - /** - * This method is called by the `instanceof` operator. - * @return {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/datatable@@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} - * - * The individual configuration values can be found in the table. - * - * @property {Object} templates Template definitions - * @property {string} templates.main Main template - * @property {Object} datasource Datasource configuration - * @property {string} datasource.selector Selector for the datasource - * @property {string} datasource.orderDelimiter Order delimiter - * @property {Object} mapping Mapping configuration - * @property {string} mapping.data Data mapping - * @property {Array} data Data - * @property {Array} headers Headers - * @property {Object} responsive Responsive configuration - * @property {number} responsive.breakpoint Breakpoint for responsive mode - * @property {Object} labels Labels - * @property {string} labels.theListContainsNoEntries Label for empty state - * @property {Object} classes Classes - * @property {string} classes.container Container class - * @property {Object} features Features - * @property {boolean} features.settings Settings feature - * @property {boolean} features.footer Footer feature - * @property {boolean} features.autoInit Auto init feature (init datasource automatically) - * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature - * @property {boolean} features.copyAll Copy all feature - * @property {boolean} features.help Help feature - * @property {Object} templateMapping Template mapping - * @property {string} templateMapping.row-key Row key - * @property {string} templateMapping.filter-id Filter id - **/ - get defaults() { - return Object.assign( - {}, - super.defaults, - { - templates: { - main: getTemplate(), - emptyState: getEmptyTemplate(), - }, - - datasource: { - selector: null, - orderDelimiter: ",", // look at initOptionsFromArguments() - }, - - mapping: { - data: "dataset", - }, - - data: [], - headers: [], - - responsive: { - breakpoint: 900, - }, - - labels: getTranslations(), - - classes: { - control: "monster-theme-control-container-1", - container: "", - row: "monster-theme-control-row-1", - }, - - features: { - settings: true, - footer: true, - autoInit: true, - doubleClickCopyToClipboard: true, - copyAll: true, - help: true, - }, - - copy: { - delimiter: ";", - quoteOpen: '"', - quoteClose: '"', - rowBreak: "\n", - }, - - templateMapping: { - "row-key": null, - "filter-id": null, - }, - }, - initOptionsFromArguments.call(this), - ); - } - - /** - * - * @param {string} selector - * @return {NodeList} - */ - getGridElements(selector) { - return this[gridElementSymbol].querySelectorAll(selector); - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-datatable"; - } - - /** - * @return {void} - */ - disconnectedCallback() { - super.disconnectedCallback(); - if (this?.[resizeObserverSymbol] instanceof ResizeObserver) { - this[resizeObserverSymbol].disconnect(); - } - } - - /** - * @return {void} - */ - connectedCallback() { - const self = this; - super.connectedCallback(); - - this[resizeObserverSymbol] = new ResizeObserver((entries) => { - updateGrid.call(self); - }); - - requestAnimationFrame(() => { - let parent = this.parentNode; - while (!(parent instanceof HTMLElement) && parent !== null) { - parent = parent.parentNode; - } - - if (parent instanceof HTMLElement) { - this[resizeObserverSymbol].observe(parent); - } - }); - } - - /** - * Get the row number of the selected rows as an array - * - * @returns {number[]} - */ - getSelectedRows() { - const rows = this.getGridElements(`[data-monster-role="select-row"]`); - const selectedRows = []; - rows.forEach((row) => { - if (row.checked) { - const key = row.parentNode.getAttribute( - "data-monster-insert-reference", - ); - const index = key.split("-").pop(); - selectedRows.push(parseInt(index, 10)); - } - }); - - return selectedRows; - } - - /** - * @return void - */ - [assembleMethodSymbol]() { - const rawKey = this.getOption("templateMapping.row-key"); - - if (rawKey === null) { - if (this.id !== null && this.id !== "") { - const rawKey = this.getOption("templateMapping.row-key"); - if (rawKey === null) { - this.setOption("templateMapping.row-key", this.id + "-row"); - } - } else { - this.setOption("templateMapping.row-key", "row"); - } - } - - if (this.id !== null && this.id !== "") { - this.setOption("templateMapping.filter-id", "" + this.id + "-filter"); - } else { - this.setOption("templateMapping.filter-id", "filter"); - } - - super[assembleMethodSymbol](); - - initControlReferences.call(this); - initEventHandler.call(this); - - getSlottedElements - .call(this, "[data-monster-role=row-action-button]", "bar") - .forEach((i, e) => { - if (e instanceof HTMLElement) { - e.style.visibility = "hidden"; - e.style.width = "max-content"; - - const pN = e.parentNode; - if (pN instanceof HTMLElement) { - pN.style.flexGrow = "10"; - pN.style.display = "flex"; - pN.style.justifyContent = "flex-start"; - } - } - }); - - getHostConfig - .call(this, getColumnVisibilityConfigKey) - .then((config) => { - const headerOrderMap = new Map(); - - getHostConfig - .call(this, getStoredOrderConfigKey) - .then((orderConfig) => { - if (isArray(orderConfig) || orderConfig.length > 0) { - for (let i = 0; i < orderConfig.length; i++) { - const item = orderConfig[i]; - const parts = item.split(" "); - const field = parts[0]; - const direction = parts[1] || DIRECTION_ASC; - headerOrderMap.set(field, direction); - } - } - }) - .then(() => { - try { - initGridAndStructs.call(this, config, headerOrderMap); - } catch (error) { - addErrorAttribute(this, error); - } - - updateColumnBar.call(this); - }) - .catch((error) => { - addErrorAttribute(this, error); - }); - }) - .catch((error) => { - addErrorAttribute(this, error); - }) - .finally(() => { - 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 (!isInstance(element, Datasource)) { - throw new TypeError("the element must be a datasource"); - } - - this[datasourceLinkedElementSymbol] = element; - - getWindow().requestAnimationFrame(() => { - handleDataSourceChanges.call(this); - if (element && "datasource" in element) { - element.datasource.attachObserver( - new Observer(handleDataSourceChanges.bind(this)), - ); - } - }); - } - }); - } - - /** - * @return {CSSStyleSheet[]} - */ - static getCSSStyleSheet() { - return [DatatableStyleSheet]; - } - - /** - * Copy a row from the datatable - * - * @param {number|string} fromIndex - * @param {number|string} toIndex - * @return {DataTable} - * @fires monster-datatable-row-copied - */ - copyRow(fromIndex, toIndex) { - const datasource = this[datasourceLinkedElementSymbol]; - if (!datasource) { - return this; - } - let d = datasource.data; - let c = clone(d); - - let rows = c; - const mapping = this.getOption("mapping.data"); - - if (mapping) { - rows = c?.[mapping]; - } - - if (rows === undefined || rows === null) { - rows = []; - } - - if (toIndex === undefined) { - toIndex = rows.length; - } - - if (isString(fromIndex)) { - fromIndex = parseInt(fromIndex); - } - if (isString(toIndex)) { - toIndex = parseInt(toIndex); - } - - if (toIndex < 0 || toIndex > rows.length) { - throw new RangeError("index out of bounds"); - } - - validateArray(rows); - validateInteger(fromIndex); - validateInteger(toIndex); - - if (fromIndex < 0 || fromIndex >= rows.length) { - throw new RangeError("index out of bounds"); - } - - rows.splice(toIndex, 0, clone(rows[fromIndex])); - datasource.data = c; - - fireCustomEvent(this, "monster-datatable-row-copied", { - index: toIndex, - }); - - return this; - } - - /** - * Remove a row from the datatable - * - * @param {number|string} index - * @return {DataTable} - * @fires monster-datatable-row-removed - */ - removeRow(index) { - const datasource = this[datasourceLinkedElementSymbol]; - if (!datasource) { - return this; - } - let d = datasource.data; - let c = clone(d); - - let rows = c; - const mapping = this.getOption("mapping.data"); - - if (mapping) { - rows = c?.[mapping]; - } - - if (rows === undefined || rows === null) { - rows = []; - } - - if (isString(index)) { - index = parseInt(index); - } - - validateArray(rows); - validateInteger(index); - - if (index < 0 || index >= rows.length) { - throw new RangeError("index out of bounds"); - } - if (mapping) { - rows = c?.[mapping]; - } - - rows.splice(index, 1); - datasource.data = c; - - fireCustomEvent(this, "monster-datatable-row-removed", { - index: index, - }); - - return this; - } - - /** - * Add a row to the datatable - * - * @param {Object} data - * @return {DataTable} - * - * @fires monster-datatable-row-added - **/ - addRow(data) { - const datasource = this[datasourceLinkedElementSymbol]; - if (!datasource) { - return this; - } - let d = datasource.data; - let c = clone(d); - - let rows = c; - - const mapping = this.getOption("mapping.data"); - if (mapping) { - rows = c?.[mapping]; - } - - if (rows === undefined || rows === null) { - rows = []; - } - - validateArray(rows); - validateObject(data); - - rows.push(data); - datasource.data = c; - - fireCustomEvent(this, "monster-datatable-row-added", { - index: rows.length - 1, - }); + /** + * This method is called by the `instanceof` operator. + * @return {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/datatable@@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} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {Object} datasource Datasource configuration + * @property {string} datasource.selector Selector for the datasource + * @property {string} datasource.orderDelimiter Order delimiter + * @property {Object} mapping Mapping configuration + * @property {string} mapping.data Data mapping + * @property {Array} data Data + * @property {Array} headers Headers + * @property {Object} responsive Responsive configuration + * @property {number} responsive.breakpoint Breakpoint for responsive mode + * @property {Object} labels Labels + * @property {string} labels.theListContainsNoEntries Label for empty state + * @property {Object} classes Classes + * @property {string} classes.container Container class + * @property {Object} features Features + * @property {boolean} features.settings Settings feature + * @property {boolean} features.footer Footer feature + * @property {boolean} features.autoInit Auto init feature (init datasource automatically) + * @property {boolean} features.doubleClickCopyToClipboard Double click copy to clipboard feature + * @property {boolean} features.copyAll Copy all feature + * @property {boolean} features.help Help feature + * @property {Object} templateMapping Template mapping + * @property {string} templateMapping.row-key Row key + * @property {string} templateMapping.filter-id Filter id + **/ + get defaults() { + return Object.assign( + {}, + super.defaults, + { + templates: { + main: getTemplate(), + emptyState: getEmptyTemplate(), + }, + + datasource: { + selector: null, + orderDelimiter: ",", // look at initOptionsFromArguments() + }, + + mapping: { + data: "dataset", + }, + + data: [], + headers: [], + + responsive: { + breakpoint: 900, + }, + + labels: getTranslations(), + + classes: { + control: "monster-theme-control-container-1", + container: "", + row: "monster-theme-control-row-1", + }, + + features: { + settings: true, + footer: true, + autoInit: true, + doubleClickCopyToClipboard: true, + copyAll: true, + help: true, + }, + + copy: { + delimiter: ";", + quoteOpen: '"', + quoteClose: '"', + rowBreak: "\n", + }, + + templateMapping: { + "row-key": null, + "filter-id": null, + }, + }, + initOptionsFromArguments.call(this), + ); + } + + /** + * + * @param {string} selector + * @return {NodeList} + */ + getGridElements(selector) { + return this[gridElementSymbol].querySelectorAll(selector); + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-datatable"; + } + + /** + * @return {void} + */ + disconnectedCallback() { + super.disconnectedCallback(); + if (this?.[resizeObserverSymbol] instanceof ResizeObserver) { + this[resizeObserverSymbol].disconnect(); + } + } + + /** + * @return {void} + */ + connectedCallback() { + const self = this; + super.connectedCallback(); + + this[resizeObserverSymbol] = new ResizeObserver((entries) => { + updateGrid.call(self); + }); + + requestAnimationFrame(() => { + let parent = this.parentNode; + while (!(parent instanceof HTMLElement) && parent !== null) { + parent = parent.parentNode; + } + + if (parent instanceof HTMLElement) { + this[resizeObserverSymbol].observe(parent); + } + }); + } + + /** + * Get the row number of the selected rows as an array + * + * @returns {number[]} + */ + getSelectedRows() { + const rows = this.getGridElements(`[data-monster-role="select-row"]`); + const selectedRows = []; + rows.forEach((row) => { + if (row.checked) { + const key = row.parentNode.getAttribute( + "data-monster-insert-reference", + ); + const index = key.split("-").pop(); + selectedRows.push(parseInt(index, 10)); + } + }); + + return selectedRows; + } + + /** + * @return void + */ + [assembleMethodSymbol]() { + const rawKey = this.getOption("templateMapping.row-key"); + + if (rawKey === null) { + if (this.id !== null && this.id !== "") { + const rawKey = this.getOption("templateMapping.row-key"); + if (rawKey === null) { + this.setOption("templateMapping.row-key", this.id + "-row"); + } + } else { + this.setOption("templateMapping.row-key", "row"); + } + } + + if (this.id !== null && this.id !== "") { + this.setOption("templateMapping.filter-id", "" + this.id + "-filter"); + } else { + this.setOption("templateMapping.filter-id", "filter"); + } + + super[assembleMethodSymbol](); + + initControlReferences.call(this); + initEventHandler.call(this); + + getSlottedElements + .call(this, "[data-monster-role=row-action-button]", "bar") + .forEach((i, e) => { + if (e instanceof HTMLElement) { + e.style.visibility = "hidden"; + e.style.width = "max-content"; + + const pN = e.parentNode; + if (pN instanceof HTMLElement) { + pN.style.flexGrow = "10"; + pN.style.display = "flex"; + pN.style.justifyContent = "flex-start"; + } + } + }); + + getHostConfig + .call(this, getColumnVisibilityConfigKey) + .then((config) => { + const headerOrderMap = new Map(); + + getHostConfig + .call(this, getStoredOrderConfigKey) + .then((orderConfig) => { + if (isArray(orderConfig) || orderConfig.length > 0) { + for (let i = 0; i < orderConfig.length; i++) { + const item = orderConfig[i]; + const parts = item.split(" "); + const field = parts[0]; + const direction = parts[1] || DIRECTION_ASC; + headerOrderMap.set(field, direction); + } + } + }) + .then(() => { + try { + initGridAndStructs.call(this, config, headerOrderMap); + } catch (error) { + addErrorAttribute(this, error); + } + + updateColumnBar.call(this); + }) + .catch((error) => { + addErrorAttribute(this, error); + }); + }) + .catch((error) => { + addErrorAttribute(this, error); + }) + .finally(() => { + 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 (!isInstance(element, Datasource)) { + throw new TypeError("the element must be a datasource"); + } + + this[datasourceLinkedElementSymbol] = element; + + getWindow().requestAnimationFrame(() => { + handleDataSourceChanges.call(this); + if (element && "datasource" in element) { + element.datasource.attachObserver( + new Observer(handleDataSourceChanges.bind(this)), + ); + } + }); + } + }); + } + + /** + * @return {CSSStyleSheet[]} + */ + static getCSSStyleSheet() { + return [DatatableStyleSheet]; + } + + /** + * Copy a row from the datatable + * + * @param {number|string} fromIndex + * @param {number|string} toIndex + * @return {DataTable} + * @fires monster-datatable-row-copied + */ + copyRow(fromIndex, toIndex) { + const datasource = this[datasourceLinkedElementSymbol]; + if (!datasource) { + return this; + } + let d = datasource.data; + let c = clone(d); + + let rows = c; + const mapping = this.getOption("mapping.data"); + + if (mapping) { + rows = c?.[mapping]; + } + + if (rows === undefined || rows === null) { + rows = []; + } + + if (toIndex === undefined) { + toIndex = rows.length; + } + + if (isString(fromIndex)) { + fromIndex = parseInt(fromIndex); + } + if (isString(toIndex)) { + toIndex = parseInt(toIndex); + } + + if (toIndex < 0 || toIndex > rows.length) { + throw new RangeError("index out of bounds"); + } + + validateArray(rows); + validateInteger(fromIndex); + validateInteger(toIndex); + + if (fromIndex < 0 || fromIndex >= rows.length) { + throw new RangeError("index out of bounds"); + } + + rows.splice(toIndex, 0, clone(rows[fromIndex])); + datasource.data = c; + + fireCustomEvent(this, "monster-datatable-row-copied", { + index: toIndex, + }); + + return this; + } + + /** + * Remove a row from the datatable + * + * @param {number|string} index + * @return {DataTable} + * @fires monster-datatable-row-removed + */ + removeRow(index) { + const datasource = this[datasourceLinkedElementSymbol]; + if (!datasource) { + return this; + } + let d = datasource.data; + let c = clone(d); + + let rows = c; + const mapping = this.getOption("mapping.data"); + + if (mapping) { + rows = c?.[mapping]; + } + + if (rows === undefined || rows === null) { + rows = []; + } + + if (isString(index)) { + index = parseInt(index); + } + + validateArray(rows); + validateInteger(index); + + if (index < 0 || index >= rows.length) { + throw new RangeError("index out of bounds"); + } + if (mapping) { + rows = c?.[mapping]; + } + + rows.splice(index, 1); + datasource.data = c; + + fireCustomEvent(this, "monster-datatable-row-removed", { + index: index, + }); + + return this; + } + + /** + * Add a row to the datatable + * + * @param {Object} data + * @return {DataTable} + * + * @fires monster-datatable-row-added + **/ + addRow(data) { + const datasource = this[datasourceLinkedElementSymbol]; + if (!datasource) { + return this; + } + let d = datasource.data; + let c = clone(d); + + let rows = c; + + const mapping = this.getOption("mapping.data"); + if (mapping) { + rows = c?.[mapping]; + } + + if (rows === undefined || rows === null) { + rows = []; + } + + validateArray(rows); + validateObject(data); + + rows.push(data); + datasource.data = c; + + fireCustomEvent(this, "monster-datatable-row-added", { + index: rows.length - 1, + }); return this; } @@ -591,7 +591,7 @@ class DataTable extends CustomElement { * @return {string} */ function getColumnVisibilityConfigKey() { - return generateUniqueConfigKey("datatable", this?.id, "columns-visibility"); + return generateUniqueConfigKey("datatable", this?.id, "columns-visibility"); } /** @@ -599,7 +599,7 @@ function getColumnVisibilityConfigKey() { * @return {string} */ function getFilterConfigKey() { - return generateUniqueConfigKey("datatable", this?.id, "filter"); + return generateUniqueConfigKey("datatable", this?.id, "filter"); } /** @@ -607,554 +607,558 @@ function getFilterConfigKey() { * @return {Promise} */ function getHostConfig(callback) { - const host = findElementWithSelectorUpwards(this, "monster-host"); - - if (!host) { - addErrorAttribute(this, "no host found"); - return Promise.resolve({}); - } - - if (!this.id) { - addErrorAttribute(this, "no id found; id is required for config"); - return Promise.resolve({}); - } - - if (!host || !isFunction(host?.getConfig)) { - throw new TypeError("the host must be a monster-host"); - } - - const configKey = callback.call(this); - return host.hasConfig(configKey).then((hasConfig) => { - if (hasConfig) { - return host.getConfig(configKey); - } else { - return {}; - } - }); + const host = findElementWithSelectorUpwards(this, "monster-host"); + + if (!host) { + addErrorAttribute(this, "no host found"); + return Promise.resolve({}); + } + + if (!this.id) { + addErrorAttribute(this, "no id found; id is required for config"); + return Promise.resolve({}); + } + + if (!host || !isFunction(host?.getConfig)) { + throw new TypeError("the host must be a monster-host"); + } + + const configKey = callback.call(this); + return host.hasConfig(configKey).then((hasConfig) => { + if (hasConfig) { + return host.getConfig(configKey); + } else { + return {}; + } + }); } /** * @private */ function updateColumnBar() { - if (!this[columnBarElementSymbol]) { - return; - } - - const columns = []; - for (const header of this.getOption("headers")) { - const mode = header.getInternal("mode"); - - if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) { - continue; - } - - columns.push({ - visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN, - name: header.label, - index: header.index, - }); - } - - this[columnBarElementSymbol].setOption("columns", columns); + if (!this[columnBarElementSymbol]) { + return; + } + + const columns = []; + for (const header of this.getOption("headers")) { + const mode = header.getInternal("mode"); + + if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) { + continue; + } + + columns.push({ + visible: mode !== ATTRIBUTE_DATATABLE_MODE_HIDDEN, + name: header.label, + index: header.index, + }); + } + + this[columnBarElementSymbol].setOption("columns", columns); } /** * @private */ function updateHeaderFromColumnBar() { - if (!this[columnBarElementSymbol]) { - return; - } + if (!this[columnBarElementSymbol]) { + return; + } - const options = this[columnBarElementSymbol].getOption("columns"); - if (!isArray(options)) return; + const options = this[columnBarElementSymbol].getOption("columns"); + if (!isArray(options)) return; - const invisibleMap = {}; + const invisibleMap = {}; - for (let i = 0; i < options.length; i++) { - const option = options[i]; - invisibleMap[option.index] = option.visible; - } + for (let i = 0; i < options.length; i++) { + const option = options[i]; + invisibleMap[option.index] = option.visible; + } - for (const header of this.getOption("headers")) { - const mode = header.getInternal("mode"); + for (const header of this.getOption("headers")) { + const mode = header.getInternal("mode"); - if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) { - continue; - } + if (mode === ATTRIBUTE_DATATABLE_MODE_FIXED) { + continue; + } - if (invisibleMap[header.index] === false) { - header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN); - } else { - header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE); - } - } + if (invisibleMap[header.index] === false) { + header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_HIDDEN); + } else { + header.setInternal("mode", ATTRIBUTE_DATATABLE_MODE_VISIBLE); + } + } } /** * @private */ function updateConfigColumnBar() { - if (!this[columnBarElementSymbol]) { - return; - } - - const options = this[columnBarElementSymbol].getOption("columns"); - if (!isArray(options)) return; - - const map = {}; - for (let i = 0; i < options.length; i++) { - const option = options[i]; - map[option.name] = option.visible; - } - - const host = findElementWithSelectorUpwards(this, "monster-host"); - if (!(host && this.id)) { - return; - } - const configKey = getColumnVisibilityConfigKey.call(this); - - try { - host.setConfig(configKey, map); - } catch (error) { - addErrorAttribute(this, error); - } + if (!this[columnBarElementSymbol]) { + return; + } + + const options = this[columnBarElementSymbol].getOption("columns"); + if (!isArray(options)) return; + + const map = {}; + for (let i = 0; i < options.length; i++) { + const option = options[i]; + map[option.name] = option.visible; + } + + const host = findElementWithSelectorUpwards(this, "monster-host"); + if (!(host && this.id)) { + return; + } + const configKey = getColumnVisibilityConfigKey.call(this); + + try { + host.setConfig(configKey, map); + } catch (error) { + addErrorAttribute(this, error); + } } /** * @private */ function initEventHandler() { - const self = this; - - const quoteOpenChar = this.getOption("copy.quoteOpen"); - const quoteCloseChar = this.getOption("copy.quoteClose"); - const delimiterChar = this.getOption("copy.delimiter"); - const rowBreak = this.getOption("copy.rowBreak"); - - self[columnBarElementSymbol].attachObserver( - new Observer((e) => { - updateHeaderFromColumnBar.call(self); - updateGrid.call(self); - updateConfigColumnBar.call(self); - }), - ); - - self[gridHeadersElementSymbol].addEventListener("click", function (event) { - let element = null; - const datasource = self[datasourceLinkedElementSymbol]; - if (!datasource) { - return; - } - - element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE); - if (element instanceof HTMLElement) { - const index = element.parentNode?.getAttribute(ATTRIBUTE_DATATABLE_INDEX); - const headers = self.getOption("headers"); - - event.preventDefault(); - - headers[index].changeDirection(); - - queueMicrotask(function () { - /** hotfix, normally this should be done via the updater, no idea why this is not possible. */ - element.setAttribute( - ATTRIBUTE_DATATABLE_SORTABLE, - `${headers[index].field} ${headers[index].direction}`, - ); - - storeOrderStatement.call(self, true); - }); - } - }); - - const eventHandlerDoubleClickCopyToClipboard = (event) => { - const element = findTargetElementFromEvent(event, "data-monster-head"); - if (element) { - let text = ""; - - if (event.shiftKey) { - const index = element.getAttribute("data-monster-insert-reference"); - if (index) { - const cols = self.getGridElements( - `[data-monster-insert-reference="${index}"]`, - ); - - const colTexts = []; - for (let i = 0; i < cols.length; i++) { - const col = cols[i]; - - if ( - col.querySelector("monster-button-bar") || - col.querySelector("monster-button") - ) { - continue; - } - - if (col.textContent) { - colTexts.push( - quoteOpenChar + col.textContent.trim() + quoteCloseChar, - ); - } - } - - text = colTexts.join(delimiterChar); - } - } else { - if ( - element.querySelector("monster-button-bar") || - element.querySelector("monster-button") - ) { - return; - } - - text = element.textContent.trim(); - } - - if (getWindow().navigator.clipboard && text) { - getWindow() - .navigator.clipboard.writeText(text) - .then( - () => {}, - (err) => {}, - ); - } - } - }; - - if (self.getOption("features.doubleClickCopyToClipboard")) { - self[gridElementSymbol].addEventListener( - "dblclick", - eventHandlerDoubleClickCopyToClipboard, - ); - } - - if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) { - this[copyAllElementSymbol].addEventListener("click", (event) => { - event.preventDefault(); - - const table = []; - let currentRow = []; - let currentIndex = null; - - const cols = self.getGridElements(`[data-monster-insert-reference]`); - const rowIndexes = new Map(); - cols.forEach((col) => { - const index = col.getAttribute("data-monster-insert-reference"); - rowIndexes.set(index, true); - }); - - rowIndexes.forEach((value, key) => { - const cols = self.getGridElements( - `[data-monster-insert-reference="${key}"]`, - ); - - for (let i = 0; i < cols.length; i++) { - const col = cols[i]; - - if ( - col.querySelector("monster-button-bar") || - col.querySelector("monster-button") - ) { - continue; - } - - if (col.textContent) { - currentRow.push( - quoteOpenChar + col.textContent.trim() + quoteCloseChar, - ); - } - } - - if (currentRow.length > 0) { - table.push(currentRow); - } - currentRow = []; - }); - - if (table.length > 0) { - const text = table.map((row) => row.join(delimiterChar)).join(rowBreak); - if (getWindow().navigator.clipboard && text) { - getWindow() - .navigator.clipboard.writeText(text) - .then( - () => {}, - (err) => {}, - ); - } - } - }); - } - - const selectRowCallback = (event) => { - const element = findTargetElementFromEvent( - event, - "data-monster-role", - "select-row", - ); - - if (!(element instanceof HTMLInputElement)) { - return; - } - - const parentNode = element.parentNode; - if (!(parentNode instanceof HTMLDivElement)) { - return; - } - - const key = element.parentNode.getAttribute( - "data-monster-insert-reference", - ); - - const row = self.getGridElements( - `[data-monster-insert-reference="${key}"]`, - ); - - const index = key.split("-").pop(); - - if (element.checked) { - row.forEach((col) => { - col.classList.add("selected"); - }); - - fireCustomEvent(self, "monster-datatable-row-selected", { - index: index, - }); - } else { - row.forEach((col) => { - col.classList.remove("selected"); - }); - - fireCustomEvent(self, "monster-datatable-row-deselected", { - index: index, - }); - } - - fireCustomEvent(this, "monster-datatable-selection-changed", {}); - - const rows = self.getGridElements(`[data-monster-role="select-row"]`); - const allSelected = Array.from(rows).every((row) => row.checked); - - const selectAll = this[gridHeadersElementSymbol].querySelector( - `[data-monster-role="select-all"]`, - ); - - getSlottedElements - .call(this, "[data-monster-role=row-action-button]", "bar") - .forEach((i, e) => { - const selected = self.getSelectedRows(); - const mode = selected.length === 0 ? "hidden" : "visible"; - if (e instanceof HTMLElement) { - e.style.visibility = mode; - } - }); - - if (selectAll) { - selectAll.checked = allSelected; - } - }; - - this[gridElementSymbol].addEventListener("click", selectRowCallback); - this[gridElementSymbol].addEventListener("touch", selectRowCallback); - - const selectAllCallback = (event) => { - const element = findTargetElementFromEvent( - event, - "data-monster-role", - "select-all", - ); - if (element) { - const mode = element.checked; - - const rows = this.getGridElements(`[data-monster-role="select-row"]`); - rows.forEach((row) => { - row.checked = mode; - }); - - if (mode) { - fireCustomEvent(this, "monster-datatable-all-rows-selected", {}); - } else { - fireCustomEvent(this, "monster-datatable-all-rows-deselected", {}); - } - - getSlottedElements - .call(this, "[data-monster-role=row-action-button]", "bar") - .forEach((i, e) => { - if (e instanceof HTMLElement) { - e.style.visibility = mode ? "visible" : "hidden"; - } - }); - - fireCustomEvent(this, "monster-datatable-selection-changed", {}); - } - }; - - this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback); - this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback); + const self = this; + + const quoteOpenChar = this.getOption("copy.quoteOpen"); + const quoteCloseChar = this.getOption("copy.quoteClose"); + const delimiterChar = this.getOption("copy.delimiter"); + const rowBreak = this.getOption("copy.rowBreak"); + + self[columnBarElementSymbol].attachObserver( + new Observer((e) => { + updateHeaderFromColumnBar.call(self); + updateGrid.call(self); + updateConfigColumnBar.call(self); + }), + ); + + self[gridHeadersElementSymbol].addEventListener("click", function (event) { + let element = null; + const datasource = self[datasourceLinkedElementSymbol]; + if (!datasource) { + return; + } + + element = findTargetElementFromEvent(event, ATTRIBUTE_DATATABLE_SORTABLE); + if (element instanceof HTMLElement) { + const index = element.parentNode?.getAttribute(ATTRIBUTE_DATATABLE_INDEX); + const headers = self.getOption("headers"); + + event.preventDefault(); + + headers[index].changeDirection(); + + queueMicrotask(function () { + /** hotfix, normally this should be done via the updater, no idea why this is not possible. */ + element.setAttribute( + ATTRIBUTE_DATATABLE_SORTABLE, + `${headers[index].field} ${headers[index].direction}`, + ); + + storeOrderStatement.call(self, true); + }); + } + }); + + const eventHandlerDoubleClickCopyToClipboard = (event) => { + const element = findTargetElementFromEvent(event, "data-monster-head"); + if (element) { + let text = ""; + + if (event.shiftKey) { + const index = element.getAttribute("data-monster-insert-reference"); + if (index) { + const cols = self.getGridElements( + `[data-monster-insert-reference="${index}"]`, + ); + + const colTexts = []; + for (let i = 0; i < cols.length; i++) { + const col = cols[i]; + + if ( + col.querySelector("monster-button-bar") || + col.querySelector("monster-button") + ) { + continue; + } + + if (col.textContent) { + colTexts.push( + quoteOpenChar + col.textContent.trim() + quoteCloseChar, + ); + } + } + + text = colTexts.join(delimiterChar); + } + } else { + if ( + element.querySelector("monster-button-bar") || + element.querySelector("monster-button") + ) { + return; + } + + text = element.textContent.trim(); + } + + if (getWindow().navigator.clipboard && text) { + getWindow() + .navigator.clipboard.writeText(text) + .then( + () => { + }, + (err) => { + }, + ); + } + } + }; + + if (self.getOption("features.doubleClickCopyToClipboard")) { + self[gridElementSymbol].addEventListener( + "dblclick", + eventHandlerDoubleClickCopyToClipboard, + ); + } + + if (self.getOption("features.copyAll") && this[copyAllElementSymbol]) { + this[copyAllElementSymbol].addEventListener("click", (event) => { + event.preventDefault(); + + const table = []; + let currentRow = []; + let currentIndex = null; + + const cols = self.getGridElements(`[data-monster-insert-reference]`); + const rowIndexes = new Map(); + cols.forEach((col) => { + const index = col.getAttribute("data-monster-insert-reference"); + rowIndexes.set(index, true); + }); + + rowIndexes.forEach((value, key) => { + const cols = self.getGridElements( + `[data-monster-insert-reference="${key}"]`, + ); + + for (let i = 0; i < cols.length; i++) { + const col = cols[i]; + + if ( + col.querySelector("monster-button-bar") || + col.querySelector("monster-button") + ) { + continue; + } + + if (col.textContent) { + currentRow.push( + quoteOpenChar + col.textContent.trim() + quoteCloseChar, + ); + } + } + + if (currentRow.length > 0) { + table.push(currentRow); + } + currentRow = []; + }); + + if (table.length > 0) { + const text = table.map((row) => row.join(delimiterChar)).join(rowBreak); + if (getWindow().navigator.clipboard && text) { + getWindow() + .navigator.clipboard.writeText(text) + .then( + () => { + }, + (err) => { + }, + ); + } + } + }); + } + + const selectRowCallback = (event) => { + const element = findTargetElementFromEvent( + event, + "data-monster-role", + "select-row", + ); + + if (!(element instanceof HTMLInputElement)) { + return; + } + + const parentNode = element.parentNode; + if (!(parentNode instanceof HTMLDivElement)) { + return; + } + + const key = element.parentNode.getAttribute( + "data-monster-insert-reference", + ); + + const row = self.getGridElements( + `[data-monster-insert-reference="${key}"]`, + ); + + const index = key.split("-").pop(); + + if (element.checked) { + row.forEach((col) => { + col.classList.add("selected"); + }); + + fireCustomEvent(self, "monster-datatable-row-selected", { + index: index, + }); + } else { + row.forEach((col) => { + col.classList.remove("selected"); + }); + + fireCustomEvent(self, "monster-datatable-row-deselected", { + index: index, + }); + } + + fireCustomEvent(this, "monster-datatable-selection-changed", {}); + + const rows = self.getGridElements(`[data-monster-role="select-row"]`); + const allSelected = Array.from(rows).every((row) => row.checked); + + const selectAll = this[gridHeadersElementSymbol].querySelector( + `[data-monster-role="select-all"]`, + ); + + getSlottedElements + .call(this, "[data-monster-role=row-action-button]", "bar") + .forEach((i, e) => { + const selected = self.getSelectedRows(); + const mode = selected.length === 0 ? "hidden" : "visible"; + if (e instanceof HTMLElement) { + e.style.visibility = mode; + } + }); + + if (selectAll) { + selectAll.checked = allSelected; + } + }; + + this[gridElementSymbol].addEventListener("click", selectRowCallback); + this[gridElementSymbol].addEventListener("touch", selectRowCallback); + + const selectAllCallback = (event) => { + const element = findTargetElementFromEvent( + event, + "data-monster-role", + "select-all", + ); + if (element) { + const mode = element.checked; + + const rows = this.getGridElements(`[data-monster-role="select-row"]`); + rows.forEach((row) => { + row.checked = mode; + }); + + if (mode) { + fireCustomEvent(this, "monster-datatable-all-rows-selected", {}); + } else { + fireCustomEvent(this, "monster-datatable-all-rows-deselected", {}); + } + + getSlottedElements + .call(this, "[data-monster-role=row-action-button]", "bar") + .forEach((i, e) => { + if (e instanceof HTMLElement) { + e.style.visibility = mode ? "visible" : "hidden"; + } + }); + + fireCustomEvent(this, "monster-datatable-selection-changed", {}); + } + }; + + this[gridHeadersElementSymbol].addEventListener("click", selectAllCallback); + this[gridHeadersElementSymbol].addEventListener("touch", selectAllCallback); } /** * @private */ function initGridAndStructs(hostConfig, headerOrderMap) { - const rowID = this.getOption("templateMapping.row-key"); - - if (!this[gridElementSymbol]) { - addErrorAttribute(this, "no grid element found"); - return; - } - - let template; - getSlottedElements.call(this).forEach((e) => { - if (e instanceof HTMLTemplateElement && e.id === rowID) { - template = e; - } - }); - - if (!template) { - addErrorAttribute(this, "no template found, please add a template"); - return; - } - - const rowCount = template.content.children.length; - - const headers = []; - - for (let i = 0; i < rowCount; i++) { - let hClass = ""; - const row = template.content.children[i]; - - let mode = ""; - if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) { - mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE); - } - - let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE); - if (!grid || grid === "" || grid === "auto") { - grid = "minmax(0, 1fr)"; - } - - let label = ""; - let labelKey = ""; - - if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) { - label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD); - labelKey = label; - - try { - if (label.startsWith("i18n:")) { - label = label.substring(5, label.length); - label = getDocumentTranslations().getText(label, label); - } - } catch (e) { - label = "i18n error " + label; - } - } - - if (!label) { - label = i + 1 + ""; - mode = ATTRIBUTE_DATATABLE_MODE_FIXED; - labelKey = label; - } - - if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) { - if (hostConfig[label] === false) { - mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN; - } else { - mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE; - } - } - - let align = ""; - if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) { - align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN); - } - - switch (align) { - case "center": - hClass = "flex-center"; - break; - case "end": - hClass = "flex-end"; - break; - case "start": - hClass = "flex-start"; - break; - default: - hClass = "flex-start"; - } - - let field = ""; - let direction = DIRECTION_NONE; - if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) { - field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim(); - const parts = field.split(" ").map((item) => item.trim()); - field = parts[0]; - - if (headerOrderMap.has(field)) { - direction = headerOrderMap.get(field); - } else if ( - parts.length === 2 && - [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1 - ) { - direction = parts[1]; - } - } - - if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) { - hClass += " hidden"; - } - - const features = []; - - if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) { - const fl = row.getAttribute(ATTRIBUTE_DATATABLE_FEATURES).split(" "); - - fl.forEach((feature) => { - features.push(feature.trim()); - - if (feature === "select") { - label = "<input type='checkbox' data-monster-role='select-all' />"; - - while (row.firstChild) { - row.removeChild(row.firstChild); - } - - const checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.setAttribute("data-monster-role", "select-row"); - row.appendChild(checkbox); - } - }); - } - - let orderTemplate = "${field} ${direction}"; - if (row.hasAttribute("data-monster-order-template")) { - orderTemplate = row.getAttribute("data-monster-order-template"); - } - - const header = new Header(); - header.setInternals({ - field: field, - label: label, - classes: hClass, - index: i, - mode: mode, - grid: grid, - labelKey: labelKey+ "-"+i, - direction: direction, - features: features, - orderTemplate: orderTemplate, - }); - - headers.push(header); - } - - this.setOption("headers", headers); - queueMicrotask(() => { - storeOrderStatement.call(this, this.getOption("features.autoInit")); - }); + const rowID = this.getOption("templateMapping.row-key"); + + if (!this[gridElementSymbol]) { + addErrorAttribute(this, "no grid element found"); + return; + } + + let template; + getSlottedElements.call(this).forEach((e) => { + if (e instanceof HTMLTemplateElement && e.id === rowID) { + template = e; + } + }); + + if (!template) { + addErrorAttribute(this, "no template found, please add a template"); + return; + } + + const rowCount = template.content.children.length; + + const headers = []; + + for (let i = 0; i < rowCount; i++) { + let hClass = ""; + const row = template.content.children[i]; + + let mode = ""; + if (row.hasAttribute(ATTRIBUTE_DATATABLE_MODE)) { + mode = row.getAttribute(ATTRIBUTE_DATATABLE_MODE); + } + + let grid = row.getAttribute(ATTRIBUTE_DATATABLE_GRID_TEMPLATE); + if (!grid || grid === "" || grid === "auto") { + grid = "minmax(0, 1fr)"; + } + + let label = ""; + let labelKey = ""; + + if (row.hasAttribute(ATTRIBUTE_DATATABLE_HEAD)) { + label = row.getAttribute(ATTRIBUTE_DATATABLE_HEAD); + labelKey = label; + + try { + if (label.startsWith("i18n:")) { + label = label.substring(5, label.length); + label = getDocumentTranslations().getText(label, label); + } + } catch (e) { + label = "i18n error " + label; + } + } + + if (!label) { + label = i + 1 + ""; + mode = ATTRIBUTE_DATATABLE_MODE_FIXED; + labelKey = label; + } + + if (isObject(hostConfig) && hostConfig.hasOwnProperty(label)) { + if (hostConfig[label] === false) { + mode = ATTRIBUTE_DATATABLE_MODE_HIDDEN; + } else { + mode = ATTRIBUTE_DATATABLE_MODE_VISIBLE; + } + } + + let align = ""; + if (row.hasAttribute(ATTRIBUTE_DATATABLE_ALIGN)) { + align = row.getAttribute(ATTRIBUTE_DATATABLE_ALIGN); + } + + switch (align) { + case "center": + hClass = "flex-center"; + break; + case "end": + hClass = "flex-end"; + break; + case "start": + hClass = "flex-start"; + break; + default: + hClass = "flex-start"; + } + + let field = ""; + let direction = DIRECTION_NONE; + if (row.hasAttribute(ATTRIBUTE_DATATABLE_SORTABLE)) { + field = row.getAttribute(ATTRIBUTE_DATATABLE_SORTABLE).trim(); + const parts = field.split(" ").map((item) => item.trim()); + field = parts[0]; + + if (headerOrderMap.has(field)) { + direction = headerOrderMap.get(field); + } else if ( + parts.length === 2 && + [DIRECTION_ASC, DIRECTION_DESC].indexOf(parts[1]) !== -1 + ) { + direction = parts[1]; + } + } + + if (mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN) { + hClass += " hidden"; + } + + const features = []; + + if (row.hasAttribute(ATTRIBUTE_DATATABLE_FEATURES)) { + const fl = row.getAttribute(ATTRIBUTE_DATATABLE_FEATURES).split(" "); + + fl.forEach((feature) => { + features.push(feature.trim()); + + if (feature === "select") { + label = "<input type='checkbox' data-monster-role='select-all' />"; + + while (row.firstChild) { + row.removeChild(row.firstChild); + } + + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.setAttribute("data-monster-role", "select-row"); + row.appendChild(checkbox); + } + }); + } + + let orderTemplate = "${field} ${direction}"; + if (row.hasAttribute("data-monster-order-template")) { + orderTemplate = row.getAttribute("data-monster-order-template"); + } + + const header = new Header(); + header.setInternals({ + field: field, + label: label, + classes: hClass, + index: i, + mode: mode, + grid: grid, + labelKey: labelKey + "-" + i, + direction: direction, + features: features, + orderTemplate: orderTemplate, + }); + + headers.push(header); + } + + this.setOption("headers", headers); + queueMicrotask(() => { + storeOrderStatement.call(this, this.getOption("features.autoInit")); + }); } /** @@ -1162,101 +1166,101 @@ function initGridAndStructs(hostConfig, headerOrderMap) { * @returns {object} */ function getTranslations() { - const locale = getLocaleOfDocument(); - switch (locale.language) { - case "de": - return { - theListContainsNoEntries: "Die Liste enthält keine Einträge", - copyAll: "Alles kopieren", - helpText: - "<p>Sie können die Werte aus einzelnen Zeilen<br>" + - "in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" + - "<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" + - "Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>", - }; - case "fr": - return { - theListContainsNoEntries: "La liste ne contient aucune entrée", - copyAll: "Copier tout", - helpText: - "<p>Vous pouvez copier les valeurs des rangées individuelles<br>" + - "dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" + - "<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" + - "Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>", - }; - case "sp": - return { - theListContainsNoEntries: "La lista no contiene entradas", - copyAll: "Copiar todo", - helpText: - "<p>Puedes copiar los valores de filas individuales<br>" + - "al portapapeles haciendo doble clic en la columna correspondiente.</p>" + - "<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" + - "Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>", - }; - case "it": - return { - theListContainsNoEntries: "L'elenco non contiene voci", - copyAll: "Copia tutto", - helpText: - "<p>Puoi copiare i valori dalle singole righe<br>" + - "negli appunti facendo doppio clic sulla colonna relativa.</p>" + - "<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" + - "Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>", - }; - case "pl": - return { - theListContainsNoEntries: "Lista nie zawiera wpisów", - copyAll: "Kopiuj wszystko", - helpText: - "<p>Możesz skopiować wartości z poszczególnych wierszy<br>" + - "do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" + - "<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" + - "Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>", - }; - case "no": - return { - theListContainsNoEntries: "Listen inneholder ingen oppføringer", - copyAll: "Kopier alt", - helpText: - "<p>Du kan kopiere verdier fra enkeltrader<br>" + - "til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" + - "<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" + - "Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>", - }; - case "dk": - return { - theListContainsNoEntries: "Listen indeholder ingen poster", - copyAll: "Kopiér alt", - helpText: - "<p>Du kan kopiere værdier fra enkelte rækker<br>" + - "til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" + - "<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" + - "Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>", - }; - case "sw": - return { - theListContainsNoEntries: "Listan innehåller inga poster", - copyAll: "Kopiera allt", - helpText: - "<p>Du kan kopiera värden från enskilda rader<br>" + - "till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" + - "<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" + - "Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>", - }; - - case "en": - default: - return { - theListContainsNoEntries: "The list contains no entries", - copyAll: "Copy all", - helpText: - "<p>You can copy the values from individual rows<br>" + - "to the clipboard by double-clicking on the relevant column.</p>" + - "<p>To copy an entire row, hold down the Shift key while clicking.<br>" + - "If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>", - }; - } + const locale = getLocaleOfDocument(); + switch (locale.language) { + case "de": + return { + theListContainsNoEntries: "Die Liste enthält keine Einträge", + copyAll: "Alles kopieren", + helpText: + "<p>Sie können die Werte aus einzelnen Zeilen<br>" + + "in die Zwischenablage kopieren, indem Sie auf die entsprechende Spalte doppelklicken.</p>" + + "<p>Um eine ganze Zeile zu kopieren, halten Sie die Umschalttaste gedrückt, während Sie klicken.<br>" + + "Wenn Sie alle Zeilen kopieren möchten, können Sie die Schaltfläche <strong>Alles kopieren</strong> verwenden.</p>", + }; + case "fr": + return { + theListContainsNoEntries: "La liste ne contient aucune entrée", + copyAll: "Copier tout", + helpText: + "<p>Vous pouvez copier les valeurs des rangées individuelles<br>" + + "dans le presse-papiers en double-cliquant sur la colonne concernée.</p>" + + "<p>Pour copier une rangée entière, maintenez la touche Maj enfoncée tout en cliquant.<br>" + + "Si vous souhaitez copier toutes les rangées, vous pouvez utiliser le bouton <strong>Copier tout</strong>.</p>", + }; + case "sp": + return { + theListContainsNoEntries: "La lista no contiene entradas", + copyAll: "Copiar todo", + helpText: + "<p>Puedes copiar los valores de filas individuales<br>" + + "al portapapeles haciendo doble clic en la columna correspondiente.</p>" + + "<p>Para copiar una fila entera, mantén presionada la tecla Shift mientras haces clic.<br>" + + "Si quieres copiar todas las filas, puedes usar el botón <strong>Copiar todo</strong>.</p>", + }; + case "it": + return { + theListContainsNoEntries: "L'elenco non contiene voci", + copyAll: "Copia tutto", + helpText: + "<p>Puoi copiare i valori dalle singole righe<br>" + + "negli appunti facendo doppio clic sulla colonna relativa.</p>" + + "<p>Per copiare un'intera riga, tieni premuto il tasto Shift mentre clicchi.<br>" + + "Se vuoi copiare tutte le righe, puoi usare il pulsante <strong>Copia tutto</strong>.</p>", + }; + case "pl": + return { + theListContainsNoEntries: "Lista nie zawiera wpisów", + copyAll: "Kopiuj wszystko", + helpText: + "<p>Możesz skopiować wartości z poszczególnych wierszy<br>" + + "do schowka, klikając dwukrotnie na odpowiednią kolumnę.</p>" + + "<p>Aby skopiować cały wiersz, przytrzymaj klawisz Shift podczas klikania.<br>" + + "Jeśli chcesz skopiować wszystkie wiersze, możesz użyć przycisku <strong>Kopiuj wszystko</strong>.</p>", + }; + case "no": + return { + theListContainsNoEntries: "Listen inneholder ingen oppføringer", + copyAll: "Kopier alt", + helpText: + "<p>Du kan kopiere verdier fra enkeltrader<br>" + + "til utklippstavlen ved å dobbeltklikke på den relevante kolonnen.</p>" + + "<p>For å kopiere en hel rad, hold nede Skift-tasten mens du klikker.<br>" + + "Hvis du vil kopiere alle radene, kan du bruke knappen <strong>Kopier alt</strong>.</p>", + }; + case "dk": + return { + theListContainsNoEntries: "Listen indeholder ingen poster", + copyAll: "Kopiér alt", + helpText: + "<p>Du kan kopiere værdier fra enkelte rækker<br>" + + "til udklipsholderen ved at dobbeltklikke på den relevante kolonne.</p>" + + "<p>For at kopiere en hel række, hold Shift-tasten nede, mens du klikker.<br>" + + "Hvis du vil kopiere alle rækker, kan du bruge knappen <strong>Kopiér alt</strong>.</p>", + }; + case "sw": + return { + theListContainsNoEntries: "Listan innehåller inga poster", + copyAll: "Kopiera allt", + helpText: + "<p>Du kan kopiera värden från enskilda rader<br>" + + "till urklipp genom att dubbelklicka på den relevanta kolumnen.</p>" + + "<p>För att kopiera en hel rad, håll ned Shift-tangenten medan du klickar.<br>" + + "Om du vill kopiera alla rader kan du använda knappen <strong>Kopiera allt</strong>.</p>", + }; + + case "en": + default: + return { + theListContainsNoEntries: "The list contains no entries", + copyAll: "Copy all", + helpText: + "<p>You can copy the values from individual rows<br>" + + "to the clipboard by double-clicking on the relevant column.</p>" + + "<p>To copy an entire row, hold down the Shift key while clicking.<br>" + + "If you want to copy all rows, you can use the <strong>Copy All</strong> button.</p>", + }; + } } /** @@ -1264,83 +1268,83 @@ function getTranslations() { * @return {string} */ export function getStoredOrderConfigKey() { - return generateUniqueConfigKey("datatable", this?.id, "stored-order"); + return generateUniqueConfigKey("datatable", this?.id, "stored-order"); } /** * @private */ function storeOrderStatement(doFetch) { - const headers = this.getOption("headers"); - const delimiter = this.getOption("datasource.orderDelimiter"); - const statement = createOrderStatement(headers, delimiter); - setDataSource.call(this, { order: statement }, doFetch); - - const host = findElementWithSelectorUpwards(this, "monster-host"); - if (!(host && this.id)) { - return; - } + const headers = this.getOption("headers"); + const delimiter = this.getOption("datasource.orderDelimiter"); + const statement = createOrderStatement(headers, delimiter); + setDataSource.call(this, {order: statement}, doFetch); - const configKey = getStoredOrderConfigKey.call(this); + const host = findElementWithSelectorUpwards(this, "monster-host"); + if (!(host && this.id)) { + return; + } - // statement explode with , and remove all empty - const list = statement.split(",").filter((item) => item.trim() !== ""); - if (list.length === 0) { - return; - } + const configKey = getStoredOrderConfigKey.call(this); + + // statement explode with , and remove all empty + const list = statement.split(",").filter((item) => item.trim() !== ""); + if (list.length === 0) { + return; + } - host.setConfig(configKey, list); + host.setConfig(configKey, list); } /** * @private */ function updateGrid() { - if (!this[gridElementSymbol]) { - throw new Error("no grid element is defined"); - } - - let gridTemplateColumns = ""; - - const headers = this.getOption("headers"); - - let styles = ""; - - for (let i = 0; i < headers.length; i++) { - const header = headers[i]; - - if ( - header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN && - header.mode !== ATTRIBUTE_DATATABLE_MODE_FIXED - ) { - styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`; - styles += `[data-monster-role=datatable] > div:nth-child(${headers.length}n+${i+1}) { display: none; }\n`; - } else { - gridTemplateColumns += `${header.grid} `; - } - } - - const sheet = new CSSStyleSheet(); - if (styles !== "") sheet.replaceSync(styles); - this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet]; - - const bodyWidth = this.parentNode.clientWidth; - - const breakpoint = this.getOption("responsive.breakpoint"); - this[dataControlElementSymbol].classList.toggle( - "small", - bodyWidth <= breakpoint, - ); - - if (bodyWidth > breakpoint) { - this[gridElementSymbol].style.gridTemplateColumns = - `${gridTemplateColumns}`; - this[gridHeadersElementSymbol].style.gridTemplateColumns = - `${gridTemplateColumns}`; - } else { - this[gridElementSymbol].style.gridTemplateColumns = "auto"; - this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto"; - } + if (!this[gridElementSymbol]) { + throw new Error("no grid element is defined"); + } + + let gridTemplateColumns = ""; + + const headers = this.getOption("headers"); + + let styles = ""; + + for (let i = 0; i < headers.length; i++) { + const header = headers[i]; + + if ( + header.mode === ATTRIBUTE_DATATABLE_MODE_HIDDEN && + header.mode !== ATTRIBUTE_DATATABLE_MODE_FIXED + ) { + styles += `[data-monster-role=datatable-headers]>[data-monster-index="${header.index}"] { display: none; }\n`; + styles += `[data-monster-role=datatable] > div:nth-child(${headers.length}n+${i + 1}) { display: none; }\n`; + } else { + gridTemplateColumns += `${header.grid} `; + } + } + + const sheet = new CSSStyleSheet(); + if (styles !== "") sheet.replaceSync(styles); + this.shadowRoot.adoptedStyleSheets = [...DataTable.getCSSStyleSheet(), sheet]; + + const bodyWidth = this.parentNode.clientWidth; + + const breakpoint = this.getOption("responsive.breakpoint"); + this[dataControlElementSymbol].classList.toggle( + "small", + bodyWidth <= breakpoint, + ); + + if (bodyWidth > breakpoint) { + this[gridElementSymbol].style.gridTemplateColumns = + `${gridTemplateColumns}`; + this[gridHeadersElementSymbol].style.gridTemplateColumns = + `${gridTemplateColumns}`; + } else { + this[gridElementSymbol].style.gridTemplateColumns = "auto"; + this[gridHeadersElementSymbol].style.gridTemplateColumns = "auto"; + } } /** @@ -1348,20 +1352,20 @@ function updateGrid() { * @param {Header[]} headers * @param {bool} doFetch */ -function setDataSource({ order }, doFetch) { - const datasource = this[datasourceLinkedElementSymbol]; +function setDataSource({order}, doFetch) { + const datasource = this[datasourceLinkedElementSymbol]; - if (!datasource) { - return; - } + if (!datasource) { + return; + } - if (isFunction(datasource?.setParameters)) { - datasource.setParameters({ order }); - } + if (isFunction(datasource?.setParameters)) { + datasource.setParameters({order}); + } - if (doFetch !== false && isFunction(datasource?.fetch)) { - datasource.fetch(); - } + if (doFetch !== false && isFunction(datasource?.fetch)) { + datasource.fetch(); + } } /** @@ -1369,30 +1373,30 @@ function setDataSource({ order }, doFetch) { * @return {DataTable} */ function initControlReferences() { - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } - this[dataControlElementSymbol] = this.shadowRoot.querySelector( - "[data-monster-role=control]", - ); + this[dataControlElementSymbol] = this.shadowRoot.querySelector( + "[data-monster-role=control]", + ); - this[gridElementSymbol] = this.shadowRoot.querySelector( - "[data-monster-role=datatable]", - ); + this[gridElementSymbol] = this.shadowRoot.querySelector( + "[data-monster-role=datatable]", + ); - this[gridHeadersElementSymbol] = this.shadowRoot.querySelector( - "[data-monster-role=datatable-headers]", - ); + this[gridHeadersElementSymbol] = this.shadowRoot.querySelector( + "[data-monster-role=datatable-headers]", + ); - this[columnBarElementSymbol] = - this.shadowRoot.querySelector("monster-column-bar"); + this[columnBarElementSymbol] = + this.shadowRoot.querySelector("monster-column-bar"); - this[copyAllElementSymbol] = this.shadowRoot.querySelector( - "[data-monster-role=copy-all]", - ); + this[copyAllElementSymbol] = this.shadowRoot.querySelector( + "[data-monster-role=copy-all]", + ); - return this; + return this; } /** @@ -1402,25 +1406,25 @@ function initControlReferences() { * @throws {Error} the datasource could not be initialized */ function initOptionsFromArguments() { - const options = { datasource: {} }; - const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR); + const options = {datasource: {}}; + const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR); - if (selector) { - options.datasource = { selector: selector }; - } + if (selector) { + options.datasource = {selector: selector}; + } - options.datasource.orderDelimiter = ","; // workaround for the missing orderDelimiter + options.datasource.orderDelimiter = ","; // workaround for the missing orderDelimiter - const breakpoint = this.getAttribute( - ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT, - ); + const breakpoint = this.getAttribute( + ATTRIBUTE_DATATABLE_RESPONSIVE_BREAKPOINT, + ); - if (breakpoint) { - options.responsive = {}; - options.responsive.breakpoint = parseInt(breakpoint); - } + if (breakpoint) { + options.responsive = {}; + options.responsive.breakpoint = parseInt(breakpoint); + } - return options; + return options; } /** @@ -1428,7 +1432,7 @@ function initOptionsFromArguments() { * @return {string} */ function getEmptyTemplate() { - return `<monster-state data-monster-role="empty-without-action"> + return `<monster-state data-monster-role="empty-without-action"> <div part="visual"> <svg width="4rem" height="4rem" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <path d="m21.5 22h-19c-1.378 0-2.5-1.121-2.5-2.5v-7c0-.07.015-.141.044-.205l3.969-8.82c.404-.896 1.299-1.475 2.28-1.475h11.414c.981 0 1.876.579 2.28 1.475l3.969 8.82c.029.064.044.135.044.205v7c0 1.379-1.122 2.5-2.5 2.5zm-20.5-9.393v6.893c0 .827.673 1.5 1.5 1.5h19c.827 0 1.5-.673 1.5-1.5v-6.893l-3.925-8.723c-.242-.536-.779-.884-1.368-.884h-11.414c-.589 0-1.126.348-1.368.885z"/> @@ -1446,8 +1450,8 @@ function getEmptyTemplate() { * @return {string} */ function getTemplate() { - // language=HTML - return ` + // language=HTML + return ` <div data-monster-role="control" part="control" data-monster-attributes="class path:classes.control"> <template id="headers-row"> <div data-monster-attributes="class path:headers-row.classes, diff --git a/source/components/datatable/filter.mjs b/source/components/datatable/filter.mjs index 74637de76eb936d32d71b588ff04804db0a0ee62..b697a14db8af1544d34167439c7d6e7e30a69223 100644 --- a/source/components/datatable/filter.mjs +++ b/source/components/datatable/filter.mjs @@ -157,7 +157,7 @@ const hashChangeSymbol = Symbol("hashChange"); * @example /examples/components/datatable/filter-advanced Advanced filter * @example /examples/components/datatable/filter-store Store filter * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/272.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/272.html * * @copyright schukai GmbH * @summary The Filter component is used to show and handle the filter values. diff --git a/source/components/datatable/save-button.mjs b/source/components/datatable/save-button.mjs index 0d6120a244f97d5db125f881c47774e8de7845b5..a1188237f55fa4aa848a307dc3d76ca3c2cce9ee 100644 --- a/source/components/datatable/save-button.mjs +++ b/source/components/datatable/save-button.mjs @@ -67,7 +67,7 @@ const badgeElementSymbol = Symbol("badgeElement"); * * @example /examples/components/datatable/save-button-simple Simple example * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/274.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/274.html * * @copyright schukai GmbH * @summary This is a save button component that can be used to save changes to a datasource. diff --git a/source/components/datatable/status.mjs b/source/components/datatable/status.mjs index 5b41b5b441c1ff7225d50ab11adc756363c791a5..8f929c080bc90be87d0fd0f7e9c37a82b64bdc4f 100644 --- a/source/components/datatable/status.mjs +++ b/source/components/datatable/status.mjs @@ -52,7 +52,7 @@ const datasourceLinkedElementSymbol = Symbol("datasourceLinkedElement"); * * @example /examples/components/datatable/datasource-status-simple Simple dataset status * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/274.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/274.html * * @copyright schukai GmbH * @summary The Status component is used to show the current status of a datasource. diff --git a/source/components/form/action-button.mjs b/source/components/form/action-button.mjs index 8f233ee58dbc2bb7c53a61062dea1837555d41cc..322a00edeceb53882fa61fcc62167f6ba8cb0051 100644 --- a/source/components/form/action-button.mjs +++ b/source/components/form/action-button.mjs @@ -48,7 +48,7 @@ const containerElementSymbol = Symbol("containerElement"); * * @example /examples/components/form/action-button * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/264.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/264.html * * @since 3.32.0 * @copyright schukai GmbH diff --git a/source/components/form/button.mjs b/source/components/form/button.mjs index 8c01e2de5ecbaee1c7e8438d3b88e2c3c2ea1411..69a3052a7c4e6e9b29e503a558142ad859166181 100644 --- a/source/components/form/button.mjs +++ b/source/components/form/button.mjs @@ -48,8 +48,8 @@ export const buttonElementSymbol = Symbol("buttonElement"); * @example /examples/components/form/button-simple Simple Button * @example /examples/components/form/button-with-click-event Button with event * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/282.html - * @issue https://localhost.alvine.dev:8443/development/issues/closed/283.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/282.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/283.html * * @copyright schukai GmbH * @summary A beautiful button that can make your life easier and also looks good. diff --git a/source/components/form/confirm-button.mjs b/source/components/form/confirm-button.mjs index f55ea79e13589cfd10f556673c8598a337a14ff7..a18dbda6fade7f6c58fea8d14edd252a19c08587 100644 --- a/source/components/form/confirm-button.mjs +++ b/source/components/form/confirm-button.mjs @@ -50,7 +50,7 @@ const cancelButtonElementSymbol = Symbol("cancelButtonElement"); * * @example /examples/components/form/confirm-button-simple simple confirm button * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/283.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/283.html * * @since 1.5.0 * @copyright schukai GmbH diff --git a/source/components/form/form.mjs b/source/components/form/form.mjs index acd5af7df8bf5b4f4454b62e267434ad2a684a07..ca812b6214f85b1508b2ec7103950f380b8cda4f 100644 --- a/source/components/form/form.mjs +++ b/source/components/form/form.mjs @@ -47,7 +47,8 @@ const debounceBindSymbol = Symbol("debounceBind"); * * @example /examples/components/form/form-simple * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/281.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/281.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/217.html * * @since 1.0.0 * @copyright schukai GmbH @@ -179,39 +180,42 @@ function initEventHandler() { this[debounceBindSymbol] = {}; 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[debounceWriteBackSymbol] instanceof DeadMansSwitch) { - try { - this[debounceWriteBackSymbol].touch(); + setTimeout(() => { + 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; - } catch (e) { - if (e.message !== "has already run") { - throw e; + } + + if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) { + try { + this[debounceWriteBackSymbol].touch(); + return; + } catch (e) { + if (e.message !== "has already run") { + throw e; + } + delete this[debounceWriteBackSymbol]; } - delete this[debounceWriteBackSymbol]; } - } - this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => { - setTimeout(() => { - this.write().catch((e) => { - addAttributeToken(this, "error", e.message || `${e}`); - }); - }, 0); + this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => { + setTimeout(() => { + this.write().catch((e) => { + addAttributeToken(this, "error", e.message || `${e}`); + }); + }, 0); + }); }); - }); - } + } + },0); } return this; diff --git a/source/components/form/popper-button.mjs b/source/components/form/popper-button.mjs index cee1df499bd6f323b73ece48d3a6cfd5a18c65bf..690ee472f83646db4cc6bfbb9dbe715e2809ed12 100644 --- a/source/components/form/popper-button.mjs +++ b/source/components/form/popper-button.mjs @@ -83,7 +83,7 @@ const arrowElementSymbol = Symbol("arrowElement"); * * @example /examples/components/form/popper-button-simple * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/283.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/283.html * * @since 1.5.0 * @copyright schukai GmbH diff --git a/source/components/form/select.mjs b/source/components/form/select.mjs index 993d29a52e6cbe85ad1b61240b5f80d8e6b01678..c6b72f84ca5d57e0746f328466bce27e209f6a91 100644 --- a/source/components/form/select.mjs +++ b/source/components/form/select.mjs @@ -349,6 +349,7 @@ class Select extends CustomControl { } const result = convertValueToSelection.call(this, value); + setSelection .call(this, result.selection) .then(() => { diff --git a/source/components/host/viewer.mjs b/source/components/host/viewer.mjs index eac978ca8a0af47c91ee3e226ea0636b3961b802..96947f3f2b2b8415d3a1c7bc2ad1716ca5ec1a7e 100644 --- a/source/components/host/viewer.mjs +++ b/source/components/host/viewer.mjs @@ -12,272 +12,11 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { - assembleMethodSymbol, - CustomElement, - registerCustomElement, -} from "../../dom/customelement.mjs"; -import "../notify/notify.mjs"; -import { ViewerStyleSheet } from "./stylesheet/viewer.mjs"; -import { instanceSymbol } from "../../constants.mjs"; -import { isString } from "../../types/is.mjs"; -import { getGlobal } from "../../types/global.mjs"; - +import { Viewer as NewViewer } from "../content/viewer.mjs"; export { Viewer }; /** - * @private - * @type {symbol} - */ -const viewerElementSymbol = Symbol("viewerElement"); - -/** - * The Viewer component is used to show a PDF, HTML or Image. - * * @copyright schukai GmbH - * @summary A simple viewer component - */ -class Viewer extends CustomElement { - /** - * This method is called by the `instanceof` operator. - * @return {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/host/viewer@@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} - * - * The individual configuration values can be found in the table. - * - * @property {Object} templates Template definitions - * @property {string} templates.main Main template - * @property {Object} classes Css classes - * @property {Object} features Feature definitions - */ - get defaults() { - return Object.assign({}, super.defaults, { - templates: { - main: getTemplate(), - }, - content: "<slot>", - classes: { - viewer: "", - }, - features: {}, - }); - } - - /** - * - * @param html - * @returns {Viewer} - */ - setContent(html) { - this.setOption("content", html); - return this; - } - - /** - * - * @param {Blob|URL|string} data - * @param {boolean} navigation - * @param {boolean} toolbar - * @param {boolean} scrollbar - */ - setPDF(data, navigation = true, toolbar = true, scrollbar = false) { - const hashes = - "#toolbar=" + - (toolbar ? "1" : "0") + - "&navpanes=" + - (navigation ? "1" : "0") + - "&scrollbar=" + - (scrollbar ? "1" : "0"); - - let pdfContent = ""; - if (isBlob(data)) { - pdfContent = URL.createObjectURL(data); - pdfContent += hashes; - } else if (isURL(data)) { - pdfContent = data; - // check if the url already contains the hashes - if (pdfContent.indexOf("#") === -1) { - pdfContent += hashes; - } - } else if (isString(data)) { - //URL.createObjectURL(data); - const blobObj = new Blob([atob(data)], { type: "application/pdf" }); - const url = window.URL.createObjectURL(blobObj); - - pdfContent = data; - } else { - throw new Error("Blob or URL expected"); - } - - const html = - '<object data="' + - pdfContent + - '" width="100%" height="100%" type="application/pdf"></object>'; - this.setContent(html); - } - - /** - * - * @param {Blob|URL|string} data - */ - setImage(data) { - if (isBlob(data)) { - data = URL.createObjectURL(data); - } else if (isURL(data)) { - // nothing to do - } else if (isString(data)) { - // nothing to do - } else { - throw new Error("Blob or URL expected"); - } - - const html = '<img src="' + data + '" alt="image" />'; - this.setContent(html); - } - - /** - * - * if the data is a string, it is interpreted as html - * if the data is a url, the html is loaded from the url and set as content - * if the data is an HTMLElement, the outerHTML is used as content - * - * @param {HTMLElement|URL|string|Blob} data - */ - setHTML(data) { - if (data instanceof Blob) { - blobToText(data) - .then((html) => { - this.setHTML(html); - }) - .catch((error) => { - throw new Error(error); - }); - - return; - } else if (data instanceof HTMLElement) { - data = data.outerHTML; - } else if (isString(data)) { - // nothing to do - } else if (isURL(data)) { - // fetch element - getGlobal() - .fetch(data) - .then((response) => { - return response.text(); - }) - .then((html) => { - this.setHTML(html); - }) - .catch((error) => { - throw new Error(error); - }); - } else { - throw new Error("HTMLElement or string expected"); - } - - this.setContent(data); - } - - /** - * - * @return {Viewer} - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - - initControlReferences.call(this); - initEventHandler.call(this); - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-viewer"; - } - - /** - * @return {CSSStyleSheet[]} - */ - static getCSSStyleSheet() { - return [ViewerStyleSheet]; - } -} - -/** - * @private - * @param variable - * @return {boolean} - */ -function isURL(variable) { - try { - new URL(variable); - return true; - } catch (error) { - return false; - } -} - -/** - * @private - * @param variable - * @return {boolean} - */ -function isBlob(variable) { - return variable instanceof Blob; -} - -/** - * @private - * @param blob - * @return {Promise<unknown>} - */ -function blobToText(blob) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsText(blob); - }); -} - -/** - * @private - * @return {Select} - * @throws {Error} no shadow-root is defined - */ -function initControlReferences() { - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } - - this[viewerElementSymbol] = this.shadowRoot.getElementById("viewer"); -} - -/** - * @private + * @deprecated since 3.102.0 use Content/Viewer instead */ -function initEventHandler() { - return this; -} - -/** - * @private - * @return {string} - */ -function getTemplate() { - // language=HTML - return ` - <div id="viewer" data-monster-role="viewer" part="viewer" data-monster-replace="path:content" data-monster-attributes="class path:classes.viewer"> - </div>`; -} - -registerCustomElement(Viewer); +class Viewer extends NewViewer {} diff --git a/source/components/layout/tabs.mjs b/source/components/layout/tabs.mjs index 536747048149aa6616198d7db0318e74dda1e8c6..eeb104ce7322afc63ab0c55f60466698b7bcab27 100644 --- a/source/components/layout/tabs.mjs +++ b/source/components/layout/tabs.mjs @@ -150,9 +150,9 @@ const resizeObserverSymbol = Symbol("resizeObserver"); * @example /examples/components/layout/tabs-active Active Tabs * @example /examples/components/layout/tabs-removable Removable Tabs * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/268.html - * @issue https://localhost.alvine.dev:8443/development/issues/closed/271.html - * @issue https://localhost.alvine.dev:8443/development/issues/closed/273.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/268.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/271.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/273.html * * @since 3.74.0 * @copyright schukai GmbH diff --git a/source/components/notify/message.mjs b/source/components/notify/message.mjs index ed53a75bd057e016c6e1d9b7e490cd1b2b861863..4e9876d4156a882ec6eeecc487e901801cf9dc44 100644 --- a/source/components/notify/message.mjs +++ b/source/components/notify/message.mjs @@ -72,7 +72,7 @@ const removeEventHandlerSymbol = Symbol("removeEventHandler"); * * @example /examples/components/notify/message-simple Message * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/269.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/269.html * * @since 1.0.0 * @copyright schukai GmbH diff --git a/source/components/notify/notify.mjs b/source/components/notify/notify.mjs index 8f26859e20b11733f2fad29af820091105814db1..0bfeaef40f0f4549cad0094c4cb937a9ca62318e 100644 --- a/source/components/notify/notify.mjs +++ b/source/components/notify/notify.mjs @@ -52,7 +52,7 @@ const queueSymbol = Symbol("queue"); * @example /examples/components/notify/notify-simple Notify * @example /examples/components/notify/notify-inline Inline Notify * - * @issue https://localhost.alvine.dev:8443/development/issues/closed/269.html + * @issue https://localhost.alvine.dev:8440/development/issues/closed/269.html * * @since 1.0.0 * @copyright schukai GmbH diff --git a/source/dom/updater.mjs b/source/dom/updater.mjs index 8d4044fe9442704a56848b9f4f780556a88332fd..a9f74d2e24273b3def431981dd3334c8e2b4b88a 100644 --- a/source/dom/updater.mjs +++ b/source/dom/updater.mjs @@ -399,6 +399,10 @@ function retrieveAndSetValue(element) { break; case "string[]": + if (value === "") { + value = []; + } + value = value.split(",").map((v) => `${v}`); break;