UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

316 lines (273 loc) 8.93 kB
/** * 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 { instanceSymbol } from "../../constants.mjs"; import { addAttributeToken } from "../../dom/attributes.mjs"; import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs"; import { assembleMethodSymbol, CustomElement, registerCustomElement, } from "../../dom/customelement.mjs"; import { findElementWithSelectorUpwards } from "../../dom/util.mjs"; import { isString } from "../../types/is.mjs"; import { State } from "../form/types/state.mjs"; import "../form/state-button.mjs"; import { ATTRIBUTE_DATASOURCE_SELECTOR } from "./constants.mjs"; import { ChangeButtonStyleSheet } from "./stylesheet/change-button.mjs"; export { ChangeButton }; /** * @private * @type {symbol} */ const stateButtonElementSymbol = Symbol("stateButtonElement"); /** * @private * @type {symbol} */ const datasetLinkedElementSymbol = Symbol("datasetLinkedElement"); /** * @private * @type {symbol} */ const overlayLinkedElementSymbol = Symbol("overlayLinkedElement"); /** * The change button component is used to change the data of a dataset. * * @fragments /fragments/components/datatable/change-button/ * * @example /examples/components/datatable/change-button-simple Simple change button * * @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. */ class ChangeButton extends CustomElement { /** * This method is called by the `instanceof` operator. * @return {symbol} */ static get [instanceSymbol]() { return Symbol.for( "@schukai/monster/components/datasource/change-button@@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 The datasource * @property {string} datasource.selector The selector of the datasource * @property {object} mapping The mapping * @property {string} mapping.data The data * @property {number} mapping.index The index * @property {Array} data The data * @return {Object} */ get defaults() { const obj = Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, labels: { button: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-grid" viewBox="0 0 16 16"> <path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5z"/> </svg>`, }, classes: { bar: "monster-button-primary", }, dataset: { selector: null, }, overlay: { selector: null, }, mapping: { data: "dataset", index: 0, }, data: {}, disabled: false, }); updateOptionsFromArguments.call(this, obj); return obj; } /** * * @return {string} */ static getTag() { return "monster-datatable-change-button"; } /** * This method is responsible for assembling the component. */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); } /** * * @return [CSSStyleSheet] */ static getCSSStyleSheet() { return [ChangeButtonStyleSheet]; } } /** * @private * @return {ChangeButton} * @throws {Error} no shadow-root is defined * @throws {TypeError} the element must be a dataset * @throws {Error} the selector must match exactly one element * @throws {TypeError} the element must be a overlay * @throws {Error} the selector must match exactly one element * @throws {Error} no reference found * @throws {Error} reference is missing or empty * @throws {Error} index is not a number * @throws {TypeError} this must be a HTMLElement */ function initControlReferences() { if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } const selector = this.getOption("dataset.selector"); if (isString(selector)) { const element = findElementWithSelectorUpwards(this, selector); if (element === null) { throw new Error("the selector must match exactly one element"); } if (!(element instanceof HTMLElement)) { throw new TypeError("the element must be a dataset"); } this[datasetLinkedElementSymbol] = element; } const selector2 = this.getOption("overlay.selector"); if (isString(selector2)) { const element = findElementWithSelectorUpwards(this, selector); if (element === null) { throw new Error("the selector must match exactly one element"); } if (!(element instanceof HTMLElement)) { throw new TypeError("the element must be a overlay"); } this[overlayLinkedElementSymbol] = element; } this[stateButtonElementSymbol] = this.shadowRoot.querySelector( "[data-monster-role=state-button]", ); if (this[stateButtonElementSymbol]) { queueMicrotask(() => { const states = { changed: new State( "changed", `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-record-circle" viewBox="0 0 16 16"> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/> <path d="M11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/> </svg>`, ), }; this[stateButtonElementSymbol].removeState(); this[stateButtonElementSymbol].setOption("states", states); this[stateButtonElementSymbol].setOption( "labels.button", this.getOption("labels.button"), ); }); } return this; } /** * @private * @returns {number} * @throws {TypeError} * @throws {Error} */ function getIndex() { if (!(this instanceof HTMLElement)) { throw new TypeError("this must be a HTMLElement"); } const row = this.closest("[data-monster-insert-reference]"); if (!row) { addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "no reference found"); throw new Error("no reference found"); } const ref = row.getAttribute("data-monster-insert-reference"); if (!ref) { addAttributeToken( this, ATTRIBUTE_ERRORMESSAGE, "reference is missing or empty", ); throw new Error("reference is missing or empty"); } const index = Number(ref.split("-").pop()); if (isNaN(index)) { throw new Error("index is not a number"); } return index; } /** * @private */ function initEventHandler() { queueMicrotask(() => { this[stateButtonElementSymbol].setOption("actions.click", () => { try { const index = getIndex.call(this); if (!isNaN(index)) { this[datasetLinkedElementSymbol].setOption("mapping.index", index); this[overlayLinkedElementSymbol].open(); } } catch (error) { addAttributeToken( this, ATTRIBUTE_ERRORMESSAGE, error.message || `${error}`, ); } }); }); } /** * @private * @param {Object} options */ function updateOptionsFromArguments(options) { const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR); if (selector) { options.datasource.selector = selector; } } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control" data-monster-attributes="disabled path:disabled | if:true"> <monster-state-button data-monster-role="state-button"></monster-state-button> </div> `; } registerCustomElement(ChangeButton);