UNPKG

@schukai/monster

Version:

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

254 lines (221 loc) 6.18 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 "../datatable/datasource/dom.mjs"; import "../form/field-set.mjs"; import { DeadMansSwitch } from "../../util/deadmansswitch.mjs"; import { DataSet } from "../datatable/dataset.mjs"; import { assembleMethodSymbol, registerCustomElement, getSlottedElements, } from "../../dom/customelement.mjs"; import { datasourceLinkedElementSymbol } from "../datatable/util.mjs"; import { FormStyleSheet } from "./stylesheet/form.mjs"; import { addAttributeToken } from "../../dom/attributes.mjs"; import { getDocument } from "../../dom/util.mjs"; import { InvalidStyleSheet } from "./stylesheet/invalid.mjs"; export { Form }; /** * @private * @type {symbol} */ const debounceWriteBackSymbol = Symbol("debounceWriteBack"); /** * @private * @type {symbol} */ const debounceBindSymbol = Symbol("debounceBind"); /** * A form control that can be used to group form elements. * * @fragments /fragments/components/form/form/ * * @example /examples/components/form/form-simple * * @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 * @summary A form control * @fires monster-options-set * @fires monster-selected * @fires monster-change * @fires monster-changed */ class Form extends DataSet { /** * @property {Object} templates Template definitions * @property {string} templates.main Main template * @property {Object} classes Class definitions * @property {string} classes.form Form class * @property {Object} writeBack Write back definitions * @property {string[]} writeBack.events Write back events * @property {Object} bind Bind definitions * @property {Object} reportValidity Report validity definitions * @property {string} reportValidity.selector Report validity selector * @property {boolean} features.mutationObserver Mutation observer feature * @property {boolean} features.writeBack Write back feature * @property {boolean} features.bind Bind feature */ get defaults() { const obj = Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, classes: { form: "", }, writeBack: { events: ["keyup", "click", "change", "drop", "touchend", "input"], }, reportValidity: { selector: "input,select,textarea,monster-select,monster-toggle-switch,monster-password", }, eventProcessing: true, }); obj["features"]["mutationObserver"] = false; obj["features"]["writeBack"] = true; return obj; } /** * * @return {string} */ static getTag() { return "monster-form"; } /** * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { return [FormStyleSheet, InvalidStyleSheet]; } /** * */ [assembleMethodSymbol]() { const selector = this.getOption("datasource.selector"); if (!selector) { this[datasourceLinkedElementSymbol] = getDocument().createElement( "monster-datasource-dom", ); } super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); initDataSourceHandler.call(this); } /** * This method is called when the component is created. * @since 3.70.0 * @return {Promise} */ refresh() { return this.write().then(() => { super.refresh(); return this; }); } /** * Run reportValidation on all child html form controls. * * @since 2.10.0 * @return {boolean} */ reportValidity() { let valid = true; const selector = this.getOption("reportValidity.selector"); const nodes = getSlottedElements.call(this, selector); nodes.forEach((node) => { if (typeof node.reportValidity === "function") { if (node.reportValidity() === false) { valid = false; } } }); return valid; } } function initDataSourceHandler() {} /** * @private * @return {initEventHandler} */ function initEventHandler() { this[debounceBindSymbol] = {}; if (this.getOption("features.writeBack") === true) { 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; } if (this[debounceWriteBackSymbol] instanceof DeadMansSwitch) { try { this[debounceWriteBackSymbol].touch(); return; } catch (e) { if (e.message !== "has already run") { throw e; } delete this[debounceWriteBackSymbol]; } } this[debounceWriteBackSymbol] = new DeadMansSwitch(200, () => { setTimeout(() => { this.write().catch((e) => { addAttributeToken(this, "error", e.message || `${e}`); }); }, 0); }); }); } }, 0); } return this; } /** * @private * @return {FilterButton} */ function initControlReferences() { if (!this.shadowRoot) { throw new Error("no shadow-root is defined"); } return this; } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control"> <form data-monster-attributes="disabled path:disabled | if:true, class path:classes.form" data-monster-role="form" part="form"> <slot data-monster-role="slot"></slot> </form> </div> `; } registerCustomElement(Form);