@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
254 lines (221 loc) • 6.18 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 "../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);