/** * Copyright schukai GmbH and contributors 2024. 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 { internalSymbol } from "../../constants.mjs"; import { CustomControl } from "../../dom/customcontrol.mjs"; import { Observer } from "../../types/observer.mjs"; import { ProxyObserver } from "../../types/proxyobserver.mjs"; import { addAttributeToken } from "../../dom/attributes.mjs"; import { assembleMethodSymbol, registerCustomElement, updaterTransformerMethodsSymbol, } from "../../dom/customelement.mjs"; import { isObject } from "../../types/is.mjs"; import { ToggleSwitchStyleSheet } from "./stylesheet/toggle-switch.mjs"; import { ATTRIBUTE_ERRORMESSAGE, ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; export { ToggleSwitch }; /** * @private * @type {symbol} */ const switchElementSymbol = Symbol("switchElement"); /** * @private * @type {symbol} */ const switchElementSymbolOn = Symbol("switchElementOn"); /** * @private * @type {symbol} */ const switchElementSymbolOff = Symbol("switchElementOff"); /** * @type {string} */ export const STATE_ON = "on"; /** * @type {string} */ export const STATE_OFF = "off"; /** * This CustomControl creates a ToggleSwitch element * * <img src="./images/switch.png"> * * * @startuml toggleswitch.png * skinparam monochrome true * skinparam shadowing false * HTMLElement <|-- CustomElement * CustomElement <|-- CustomControl * CustomControl <|-- ToggleSwitch * @enduml * * @since 3.57.0 * @copyright schukai GmbH * @memberOf Monster.Components.Form * @summary A simple Switch */ class ToggleSwitch 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/toggle-switch@@instance", ); } static getTag() { return "monster-toggle-switch"; } /** * 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 {string} value=current value of the element * @property {Boolean} disabled=disabled=false Disabled state * @property {Object} classes * @property {string} classes.on=specifies the class for the on state. * @property {string} classes.off=specifies the class for the off state. * @property {Object} values * @property {string} values.off=specifies the value of the element if it is not selected * @property {Object} labels * @property {string} labels.on=specifies the label for the on state. * @property {string} labels.off=specifies the label for the off state. * @property {Object} templates * @property {string} templates.main=specifies the main template used by the control. * * @since 3.57.0 */ get defaults() { return Object.assign({}, super.defaults, { value: null, disabled: false, classes: { on: "monster-theme-primary-3", off: "monster-theme-primary-2", }, values: { on: "on", off: "off", }, labels: { "toggle-switch-on": "ON", "toggle-switch-off": "OFF", }, templates: { main: getTemplate(), }, }); } /** * * @return {Monster.Components.Form.ToggleSwitch} */ [assembleMethodSymbol]() { const self = this; super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); /** * init value to off * if the value was not defined before inserting it into the HTML */ if (self.getOption("value") === null) { self.setOption("value", self.getOption("values.off")); } /** * value from attribute */ if (self.hasAttribute("value")) { self.setOption("value", self.getAttribute("value")); } /** * validate value */ validateAndSetValue.call(self); if (this.state === STATE_ON) { toggleClassOn.call(self); } else { toggleClassOff.call(self); } /** * is called when options changed */ self[internalSymbol].attachObserver( new Observer(function () { if (isObject(this) && this instanceof ProxyObserver) { validateAndSetValue.call(self); toggleClass.call(self); } }), ); return this; } /** * updater transformer methods for pipe * * @return {function} */ [updaterTransformerMethodsSymbol]() { return { "state-callback": (Wert) => { return this.state; }, }; } /** * @return [CSSStyleSheet] */ static getCSSStyleSheet() { return [ToggleSwitchStyleSheet]; } /** * toggle switch * * ``` * e = document.querySelector('monster-toggle-switch'); * e.click() * ``` */ click() { toggleValues.call(this); } /** * toggle switch on/off * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggle() * ``` * * @return {ToggleSwitch} */ toggle() { this.click(); return this; } /** * toggle switch on * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggleOn() * ``` * * @return {ToggleSwitch} */ toggleOn() { this.setOption("value", this.getOption("values.on")); return this; } /** * toggle switch off * * ``` * e = document.querySelector('monster-toggle-switch'); * e.toggleOff() * ``` * * @return {ToggleSwitch} */ toggleOff() { this.setOption("value", this.getOption("values.off")); return this; } /** * returns the status of the element * * ``` * e = document.querySelector('monster-toggle-switch'); * console.log(e.state) * // ↦ off * ``` * * @return {string} */ get state() { return this.getOption("value") === this.getOption("values.on") ? STATE_ON : STATE_OFF; } /** * The current value of the Switch * * ``` * e = document.querySelector('monster-toggle-switch'); * console.log(e.value) * // ↦ on * ``` * * @return {string} */ get value() { return this.state === STATE_ON ? this.getOption("values.on") : this.getOption("values.off"); } /** * Set value * * ``` * e = document.querySelector('monster-toggle-switch'); * e.value="on" * ``` * * @property {string} value */ set value(value) { this.setOption("value", value); } } /** * @private */ function initControlReferences() { this[switchElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}=switch]`, ); } /** * @private */ function toggleClassOn() { this[switchElementSymbol].classList.remove(this.getOption("classes.off")); // change color this[switchElementSymbol].classList.add(this.getOption("classes.on")); // change color } /** * @private */ function toggleClassOff() { this[switchElementSymbol].classList.remove(this.getOption("classes.on")); // change color this[switchElementSymbol].classList.add(this.getOption("classes.off")); // change color } /** * @private */ function toggleClass() { if (this.getOption("value") === this.getOption("values.on")) { toggleClassOn.call(this); } else { toggleClassOff.call(this); } } /** * @private */ function toggleValues() { if (this.getOption("disabled") === true) { return; } if (this.getOption("value") === this.getOption("values.on")) { this.setOption("value", this.getOption("values.off")); this?.setFormValue(this.getOption("value")); // set form value } else { this.setOption("value", this.getOption("values.on")); this?.setFormValue(this.getOption("values.off")); // set form value } this.setOption("state", this.state); } /** * @private */ function validateAndSetValue() { const value = this.getOption("value"); const validatedValues = []; validatedValues.push(this.getOption("values.on")); validatedValues.push(this.getOption("values.off")); if (validatedValues.includes(value) === false) { addAttributeToken( this, ATTRIBUTE_ERRORMESSAGE, 'The value "' + value + '" must be "' + this.getOption("values.on") + '" or "' + this.getOption("values.off"), ); this.setOption("disabled", true); this.formDisabledCallback(true); } else { this.setOption("disabled", false); this.formDisabledCallback(false); } } /** * @private * @return {initEventHandler} */ function initEventHandler() { const self = this; self.addEventListener("keyup", function (event) { if (event.code === "Space") { self[switchElementSymbol].click(); } }); self.addEventListener("click", function (event) { toggleValues.call(self); }); return this; } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control" tabindex="0"> <div class="switch" data-monster-role="switch" data-monster-attributes="data-monster-state path:value | call:state-callback"> <div class="label on" data-monster-replace="path:labels.toggle-switch-on"></div> <div class="label off" data-monster-replace="path:labels.toggle-switch-off"></div> <div class="switch-slider"></div> </div> </div>`; } registerCustomElement(ToggleSwitch);