Skip to content
Snippets Groups Projects
Select Git revision
  • f6a359d86000d96024648e939efbd8adb0a7f2ef
  • master default protected
  • 1.31
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
  • 4.31.0
  • 4.30.1
  • 4.30.0
  • 4.29.1
  • 4.29.0
  • 4.28.0
  • 4.27.0
  • 4.26.0
  • 4.25.5
  • 4.25.4
  • 4.25.3
  • 4.25.2
  • 4.25.1
23 results

tutorial-tutorial.html

Blame
  • button.mjs 8.38 KiB
    /**
     * Copyright schukai GmbH and contributors 2023. All Rights Reserved.
     * Node module: @schukai/monster
     * This file is licensed under the AGPLv3 License.
     * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
     */
    import { instanceSymbol } from "../../constants.mjs";
    import { addAttributeToken } from "../../dom/attributes.mjs";
    import {
    	ATTRIBUTE_ERRORMESSAGE,
    	ATTRIBUTE_ROLE,
    } from "../../dom/constants.mjs";
    import { CustomControl } from "../../dom/customcontrol.mjs";
    import {
    	assembleMethodSymbol,
    	attributeObserverSymbol,
    	registerCustomElement,
    } from "../../dom/customelement.mjs";
    import { findTargetElementFromEvent } from "../../dom/events.mjs";
    import { isFunction } from "../../types/is.mjs";
    import { ATTRIBUTE_BUTTON_CLASS } from "./constants.mjs";
    import { ButtonStyleSheet } from "./stylesheet/button.mjs";
    import { RippleStyleSheet } from "../stylesheet/ripple.mjs";
    import { fireCustomEvent } from "../../dom/events.mjs";
    import { isString } from "../../types/is.mjs";
    
    export { Button };
    
    /**
     * @private
     * @type {symbol}
     */
    export const buttonElementSymbol = Symbol("buttonElement");
    
    /**
     * This CustomControl creates a button element with a variety of options.
     *
     * <img src="./images/button.png">
     *
     * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
     *
     * You can create this control either by specifying the HTML tag <monster-button />` directly in the HTML or using
     * Javascript via the `document.createElement('monster-button');` method.
     *
     * ```html
     * <monster-button></monster-button>
     * ```
     *
     * Or you can create this CustomControl directly in Javascript:
     *
     * ```js
     * import {Button} from '@schukai/component-form/source/button.js';
     * document.createElement('monster-button');
     * ```
     *
     * The `data-monster-button-class` attribute can be used to change the CSS class of the button.
     *
     * @externalExample ../../../example/components/form/button.mjs
     * @startuml button.png
     * skinparam monochrome true
     * skinparam shadowing false
     * HTMLElement <|-- CustomElement
     * CustomElement <|-- CustomControl
     * CustomControl <|-- Button
     * @enduml
     *
     * @since 1.5.0
     * @copyright schukai GmbH
     * @memberOf Monster.Components.Form
     * @summary A simple button
     */
    class Button extends CustomControl {
    	/**
    	 * This method is called by the `instanceof` operator.
    	 * @returns {symbol}
    	 * @since 2.1.0
    	 */
    	static get [instanceSymbol]() {
    		return Symbol.for("@schukai/monster/components/form/button@@instance");
    	}
    
    	/**
    	 *
    	 * @return {Monster.Components.Form.Button}
    	 */
    	[assembleMethodSymbol]() {
    		super[assembleMethodSymbol]();
    		initControlReferences.call(this);
    		initEventhandler.call(this);
    		return this;
    	}
    
    	/**
    	 * The Button.click() method simulates a click on the internal button element.
    	 *
    	 * @since 3.27.0
    	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
    	 */
    	click() {
    		if (this.getOption("disabled") === true) {
    			return;
    		}
    
    		if (
    			this[buttonElementSymbol] &&
    			isFunction(this[buttonElementSymbol].click)
    		) {
    			this[buttonElementSymbol].click();
    		}
    	}
    
    	/**
    	 * The Button.focus() method sets focus on the internal button element.
    	 *
    	 * @since 3.27.0
    	 * @param {Object} options
    	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
    	 */
    	focus(options) {
    		if (this.getOption("disabled") === true) {
    			return;
    		}
    
    		if (
    			this[buttonElementSymbol] &&
    			isFunction(this[buttonElementSymbol].focus)
    		) {
    			this[buttonElementSymbol].focus(options);
    		}
    	}
    
    	/**
    	 * The Button.blur() method removes focus from the internal button element.
    	 */
    	blur() {
    		if (
    			this[buttonElementSymbol] &&
    			isFunction(this[buttonElementSymbol].blur)
    		) {
    			this[buttonElementSymbol].blur();
    		}
    	}
    
    	/**
    	 * This method determines which attributes are to be monitored by `attributeChangedCallback()`.
    	 *
    	 * @return {string[]}
    	 */
    	static get observedAttributes() {
    		const attributes = super.observedAttributes;
    		attributes.push(ATTRIBUTE_BUTTON_CLASS);
    		return attributes;
    	}
    
    	/**
    	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
    	 * @return {boolean}
    	 */
    	static get formAssociated() {
    		return true;
    	}
    
    	/**
    	 * The current selection of the Select
    	 *
    	 * ```
    	 * e = document.querySelector('monster-select');
    	 * console.log(e.value)
    	 * // ↦ 1
    	 * // ↦ ['1','2']
    	 * ```
    	 *
    	 * @property {string|array}
    	 */
    	get value() {
    		return this.getOption("value");
    	}
    
    	/**
    	 * Set selection
    	 *
    	 * ```
    	 * e = document.querySelector('monster-select');
    	 * e.value=1
    	 * ```
    	 *
    	 * @property {string|array} value
    	 * @since 1.2.0
    	 * @throws {Error} unsupported type
    	 */
    	set value(value) {
    		this.setOption("value", value);
    		try {
    			this?.setFormValue(this.value);
    		} catch (e) {
    			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
    		}
    	}
    
    	/**
    	 * To set the options via the html tag the attribute `data-monster-options` must be used.
    	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
    	 *
    	 * The individual configuration values can be found in the table.
    	 *
    	 * @property {Object} templates Template definitions
    	 * @property {string} templates.main Main template
    	 * @property {Object} labels Labels
    	 * @property {string} labels.button=<slot></slot> Button label
    	 * @property {Object} actions Callbacks
    	 * @property {string} actions.click="throw Error" Callback when clicked
    	 * @property {Object} classes CSS classes
    	 * @property {string} classes.button="monster-button-primary" CSS class for the button
    	 * @property {boolean} disabled=false Disabled state
    	 * @property {Object} effects Effects
    	 * @property {boolean} effects.ripple=true Ripple effect
    	 */
    	get defaults() {
    		return Object.assign({}, super.defaults, {
    			templates: {
    				main: getTemplate(),
    			},
    			labels: {
    				button: "<slot></slot>",
    			},
    			classes: {
    				button: "monster-button-primary",
    			},
    			disabled: false,
    			actions: {
    				click: (e) => {
    					throw new Error("the click action is not defined");
    				},
    			},
    			effects: {
    				ripple: true,
    			},
    			value: null,
    		});
    	}
    
    	/**
    	 *
    	 * @return {string}
    	 */
    	static getTag() {
    		return "monster-button";
    	}
    
    	/**
    	 *
    	 * @return {Array<CSSStyleSheet>}
    	 */
    	static getCSSStyleSheet() {
    		return [RippleStyleSheet, ButtonStyleSheet];
    	}
    }
    
    /**
     * @private
     * @return {initEventhandler}
     * @fires Monster.Components.Form.event:monster-button-clicked
     */
    function initEventhandler() {
    	const self = this;
    	const button = this[buttonElementSymbol];
    
    	const type = "click";
    
    	button.addEventListener(type, function (event) {
    		const callback = self.getOption("actions.click");
    
    		fireCustomEvent(self, "monster-button-clicked", {
    			button: self,
    		});
    
    		if (!isFunction(callback)) {
    			return;
    		}
    
    		const element = findTargetElementFromEvent(
    			event,
    			ATTRIBUTE_ROLE,
    			"control",
    		);
    
    		if (!(element instanceof Node && self.hasNode(element))) {
    			return;
    		}
    
    		callback.call(self, event);
    	});
    
    	if (self.getOption("effects.ripple")) {
    		button.addEventListener("click", createRipple.bind(self));
    	}
    
    	// data-monster-options
    	self[attributeObserverSymbol][ATTRIBUTE_BUTTON_CLASS] = function (value) {
    		self.setOption("classes.button", value);
    	};
    
    	return this;
    }
    
    /**
     * @private
     */
    function initControlReferences() {
    	this[buttonElementSymbol] = this.shadowRoot.querySelector(
    		`[${ATTRIBUTE_ROLE}=button]`,
    	);
    }
    
    /**
     * @private
     * @return {string}
     */
    function getTemplate() {
    	// language=HTML
    	return `
            <div data-monster-role="control" part="control">
                <button data-monster-attributes="disabled path:disabled | if:true, class path:classes.button"
                        data-monster-role="button"
                        part="button"
                        data-monster-replace="path:labels.button"></button>
            </div>`;
    }
    
    function createRipple(event) {
    	const button = this[buttonElementSymbol];
    
    	const circle = document.createElement("span");
    	const diameter = Math.max(button.clientWidth, button.clientHeight);
    	const radius = diameter / 2;
    
    	circle.style.width = circle.style.height = `${diameter}px`;
    	circle.style.left = `${event.clientX - button.offsetLeft - radius}px`;
    	circle.style.top = `${event.clientY - button.offsetTop - radius}px`;
    	circle.classList.add("monster-fx-ripple");
    
    	const ripples = button.getElementsByClassName("monster-fx-ripple");
    	for (const ripple of ripples) {
    		ripple.remove();
    	}
    
    	button.appendChild(circle);
    }
    
    registerCustomElement(Button);