@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
253 lines (225 loc) • 6.97 kB
JavaScript
/**
* 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 {
assembleMethodSymbol,
registerCustomElement,
} from "../../dom/customelement.mjs";
import { PopperButton } from "./popper-button.mjs";
import { ActionButtonStyleSheet } from "./stylesheet/action-button.mjs";
import { isObject, isIterable, isString, isFunction } from "../../types/is.mjs";
import { Observer } from "../../types/observer.mjs";
import { getDocumentTranslations } from "../../i18n/translations.mjs";
import {
findTargetElementFromEvent,
fireCustomEvent,
} from "../../dom/events.mjs";
import { addAttributeToken } from "../../dom/attributes.mjs";
import {
ATTRIBUTE_ERRORMESSAGE,
ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";
import { addErrorAttribute } from "../../dom/error.mjs";
export { ActionButton };
/**
* @private
* @type {symbol}
*/
const containerElementSymbol = Symbol("containerElement");
/**
* A button that opens a popper element with possible actions.
*
* @fragments /fragments/components/form/action-button
*
* @example /examples/components/form/action-button
*
* @issue https://localhost.alvine.dev:8440/development/issues/closed/264.html
*
* @since 3.32.0
* @copyright schukai GmbH
* @summary The ActionButton is a button that opens a popper element with possible actions
*/
class ActionButton extends PopperButton {
/**
* This method is called by the `instanceof` operator.
* @return {symbol}
*/
static get [instanceSymbol]() {
return Symbol.for(
"@schukai/monster/components/form/action-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} labels
* @property {string} labels.button The label of the button
* @property {object[]} buttons
* @property {string} buttons[].label The label of the button
* @property {string} buttons[].class The CSS class of the button
* @property {function} buttons[].action The action of the button
* @property {object} templates
* @property {string} templates.main The template of the button
* @extends {PopperButton.defaults}
*/
get defaults() {
return Object.assign({}, super.defaults, {
templates: {
main: getTemplate(),
},
labels: {
button: "<slot></slot>",
},
buttons: [],
});
}
/**
*
* @return {ActionButton}
* @fires monster-action-button-show-dialog
*/
showDialog() {
if (this.getOption("buttons").length === 0) {
return this;
}
return super.showDialog();
}
/**
*
* @return {ActionButton}
*/
[assembleMethodSymbol]() {
super[assembleMethodSymbol]();
initControlReferences.call(this);
initEventHandler.call(this);
return this;
}
/**
* @return {string}
*/
static getTag() {
return "monster-action-button";
}
/**
* @return {CSSStyleSheet[]}
*/
static getCSSStyleSheet() {
const styles = super.getCSSStyleSheet();
styles.push(ActionButtonStyleSheet);
return styles;
}
}
/**
* @private
* @return {ActionButton}
*/
function initEventHandler() {
this[containerElementSymbol].addEventListener("click", (event) => {
const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "button");
const attr = element.getAttribute("data-monster-insert-reference");
if (attr) {
const index = attr.split("-")[1];
const b = this.getOption("buttons." + index);
if (isObject(b) && isFunction(b?.action)) {
b.action(event, element, this);
}
}
});
let memButtons = "";
this.attachObserver(
new Observer(() => {
if (JSON.stringify(this.getOption("buttons")) !== memButtons) {
try {
updateButtonsI18n.call(this);
} catch (e) {
addErrorAttribute(this, e.message);
}
memButtons = JSON.stringify(this.getOption("buttons"));
}
}),
);
return this;
}
/**
* @private
* @returns {updateButtonsI18n}
*/
function updateButtonsI18n() {
const translations = getDocumentTranslations();
if (!translations) {
return this;
}
const buttons = this.getOption("buttons");
if (!isIterable(buttons)) {
return this;
}
for (const key in buttons) {
const def = buttons[key]["label"];
if (isString(def)) {
const text = translations.getText(def, def);
if (text !== def) {
this.setOption(`buttons.${key}.label`, text);
}
continue;
}
throw new Error("Invalid labels definition");
}
return this;
}
/**
* @private
* @return {ActionButton}
*/
function initControlReferences() {
this[containerElementSymbol] = this.shadowRoot.querySelector(
`[${ATTRIBUTE_ROLE}=container]`,
);
return this;
}
/**
* @private
* @return {string}
*/
function getTemplate() {
// language=HTML
return `
<template id="btn">
<monster-message-state-button data-monster-role="button" part="button"
data-monster-attributes="id path:btn.id,
data-monster-option-classes-button path:btn.class,
disabled path:btn.disabled | if:true,
data-monster-url path:btn.url"
data-monster-option-actions-click=""
data-monster-option-popper-placement="right"
data-monster-replace="path:btn.label"></monster-message-state-button>
</template>
<div data-monster-role="control" part="control">
<button data-monster-attributes="class path:classes.button"
data-monster-role="button" part="button"
data-monster-replace="path:labels.button"
></button>
<div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
<div data-monster-role="arrow"></div>
<div part="content" class="flex" data-monster-role="container">
<div part="buttons" data-monster-role="buttons" data-monster-insert="btn path:buttons"
tabindex="-1"></div>
</div>
</div>
</div>
`;
}
registerCustomElement(ActionButton);