Skip to content
Snippets Groups Projects
Select Git revision
  • aaf5b85b31e58fda016a2631caa6cd27a1ba2fd5
  • master default protected
  • 1.31
  • 4.38.3
  • 4.38.2
  • 4.38.1
  • 4.38.0
  • 4.37.2
  • 4.37.1
  • 4.37.0
  • 4.36.0
  • 4.35.0
  • 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
23 results

customcontrol.mjs

Blame
  • Volker Schukai's avatar
    0f105d32
    History
    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"));
        };
    }