Select Git revision
customcontrol.mjs

Volker Schukai authored
customcontrol.mjs 10.67 KiB
/**
* Copyright schukai GmbH and contributors 2022. 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 { extend } from "../data/extend.mjs";
import { ATTRIBUTE_VALUE } from "./constants.mjs";
import { CustomElement, attributeObserverSymbol } from "./customelement.mjs";
import { instanceSymbol } from "../constants.mjs";
export { CustomControl };
/**
* @private
* @type {symbol}
*/
const attachedInternalSymbol = Symbol("attachedInternal");
/**
* To define a new HTML control we need the power of CustomElement
*
* IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called
* with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM.
*
* <img src="./images/customcontrol-class.png">
*
* This control uses `attachInternals()` to integrate the control into a form.
* If the target environment does not support this method, the [polyfill](https://www.npmjs.com/package/element-internals-polyfill ) can be used.
*
* You can create the object via the function `document.createElement()`.
*
* @startuml customcontrol-class.png
* skinparam monochrome true
* skinparam shadowing false
* HTMLElement <|-- CustomElement
* CustomElement <|-- CustomControl
* @enduml
*
* @summary A base class for customcontrols based on CustomElement
* @see {@link https://www.npmjs.com/package/element-internals-polyfill}
* @see {@link https://github.com/WICG/webcomponents}
* @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements}
* @license AGPLv3
* @since 1.14.0
* @copyright schukai GmbH
* @memberOf Monster.DOM
*/
class CustomControl extends CustomElement {
/**
* IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>.
*
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
* @summary create new Instance
*/
constructor() {
super();
if (typeof this["attachInternals"] === "function") {
/**
* currently only supported by chrome
* @property {Object}
* @private
*/
this[attachedInternalSymbol] = this.attachInternals();
}
initObserver.call(this);
}
/**
* This method is called by the `instanceof` operator.
* @returns {symbol}
* @since 2.1.0
*/
static get [instanceSymbol]() {
return Symbol.for("@schukai/monster/dom/custom-control");
}
/**
* This method determines which attributes are to be monitored by `attributeChangedCallback()`.
*
* @return {string[]}
* @since 1.15.0
*/
static get observedAttributes() {
const list = super.observedAttributes;
list.push(ATTRIBUTE_VALUE);
return list;
}
/**
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
* @since 1.14.0
* @return {boolean}
*/
static get formAssociated() {
return true;
}
/**
* Derived classes can override and extend this method as follows.
*
* ```
* get defaults() {
* return extends{}, super.defaults, {
* myValue:true
* });
* }
* ```
*
* @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals}
* @return {object}
* @since 1.14.0
*/
get defaults() {
return extend({}, super.defaults);
}
/**
* Must be overridden by a derived class and return the value of the control.
*
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @since 1.14.0
* @throws {Error} the value getter must be overwritten by the derived class
*/
get value() {
throw Error("the value getter must be overwritten by the derived class");
}
/**
* Must be overridden by a derived class and return the value of the control.
*
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @param {*} value
* @since 1.14.0
* @throws {Error} the value setter must be overwritten by the derived class
*/
set value(value) {
throw Error("the value setter must be overwritten by the derived class");
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {NodeList}
* @since 1.14.0
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/labels}
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
*/
get labels() {
return getInternal.call(this)?.labels;
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {string|null}
*/
get name() {
return this.getAttribute("name");
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {string}
*/
get type() {
return this.constructor.getTag();
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {ValidityState}
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/validity}
*/
get validity() {
return getInternal.call(this)?.validity;
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {string}
* @since 1.14.0
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/validationMessage
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
*/
get validationMessage() {
return getInternal.call(this)?.validationMessage;
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {boolean}
* @since 1.14.0
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/willValidate
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
*/
get willValidate() {
return getInternal.call(this)?.willValidate;
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {CustomStateSet}
* @since 1.14.0
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
*/
get states() {
return getInternal.call(this)?.states;
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {HTMLFontElement|null}
* @since 1.14.0
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/form
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
*/
get form() {
return getInternal.call(this)?.form;
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* ```
* // Use the control's name as the base name for submitted data
* const n = this.getAttribute('name');
* const entries = new FormData();
* entries.append(n + '-first-name', this.firstName_);
* entries.append(n + '-last-name', this.lastName_);
* this.setFormValue(entries);
* ```
*
* @param {File|string|FormData} value
* @param {File|string|FormData} state
* @since 1.14.0
* @return {undefined}
* @throws {DOMException} NotSupportedError
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setFormValue
*/
setFormValue(value, state) {
getInternal.call(this).setFormValue(value, state);
}
/**
*
* @param {object} flags
* @param {string|undefined} message
* @param {HTMLElement} anchor
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setValidity
* @since 1.14.0
* @return {undefined}
* @throws {DOMException} NotSupportedError
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
*/
setValidity(flags, message, anchor) {
getInternal.call(this).setValidity(flags, message, anchor);
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/checkValidity
* @since 1.14.0
* @return {boolean}
* @throws {DOMException} NotSupportedError
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
*/
checkValidity() {
return getInternal.call(this)?.checkValidity();
}
/**
* This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals)
*
* @return {boolean}
* @since 1.14.0
* @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/reportValidity
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
* @throws {DOMException} NotSupportedError
*/
reportValidity() {
return getInternal.call(this)?.reportValidity();
}
}
/**
* @private
* @return {object}
* @throws {Error} the ElementInternals is not supported and a polyfill is necessary
* @this CustomControl
*/
function getInternal() {
const self = this;
if (!(attachedInternalSymbol in this)) {
throw new Error("ElementInternals is not supported and a polyfill is necessary");
}
return this[attachedInternalSymbol];
}
/**
* @private
* @return {object}
* @this CustomControl
*/
function initObserver() {
const self = this;
// value
self[attributeObserverSymbol]["value"] = () => {
self.setOption("value", self.getAttribute("value"));
};
}