UNPKG

@schukai/monster

Version:

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

276 lines (233 loc) 6.82 kB
/** * Copyright © Volker Schukai 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 Volker Schukai. * * SPDX-License-Identifier: AGPL-3.0 */ import { instanceSymbol } from "../../constants.mjs"; import { assembleMethodSymbol, registerCustomElement, } from "../../dom/customelement.mjs"; import { addErrorAttribute } from "../../dom/error.mjs"; import { getDocument } from "../../dom/util.mjs"; import { isArray, isObject, isString } from "../../types/is.mjs"; import { CustomElement } from "../../dom/customelement.mjs"; export { SelectLink }; const sourceElementSymbol = Symbol("sourceElement"); const targetElementSymbol = Symbol("targetElement"); const observerSymbol = Symbol("observer"); const handlerSymbol = Symbol("handler"); const boundSymbol = Symbol("bound"); class SelectLink extends CustomElement { static get [instanceSymbol]() { return Symbol.for("@schukai/monster/components/form/select-link@@instance"); } static getTag() { return "monster-select-link"; } get defaults() { return Object.assign({}, super.defaults, { shadowMode: false, source: "", target: "", param: "", emptyValue: "", disableTarget: true, clearSelection: true, clearOptions: true, clearTotalMessage: true, clearMessageOnValue: true, emptyMessage: "", autoFetch: true, autoFetchOnEmpty: false, syncOnInit: true, events: [ "monster-selected", "monster-changed", "monster-selection-removed", "monster-selection-cleared", ], debug: false, }); } [assembleMethodSymbol]() { super[assembleMethodSymbol](); bindSelects.call(this); return this; } disconnectedCallback() { unbindSelects.call(this); } } function resolveElement(value) { if (value instanceof HTMLElement) { return value; } if (!isString(value) || value.trim() === "") { return null; } const selector = value.trim(); const doc = getDocument(); let element = null; try { element = doc.querySelector(selector); } catch (e) { element = null; } if (!element) { element = doc.getElementById(selector.replace(/^#/, "")); } return element instanceof HTMLElement ? element : null; } function bindSelects() { const source = resolveElement(this.getOption("source")); const target = resolveElement(this.getOption("target")); if (!(source && target)) { if (!this[observerSymbol] && getDocument().body) { const observer = new MutationObserver(() => { bindSelects.call(this); }); observer.observe(getDocument().body, { childList: true, subtree: true, }); this[observerSymbol] = observer; } return; } if (this[observerSymbol]) { this[observerSymbol].disconnect(); delete this[observerSymbol]; } this[sourceElementSymbol] = source; this[targetElementSymbol] = target; if (this[boundSymbol]) { return; } const events = this.getOption("events"); const handler = () => syncTarget.call(this); this[handlerSymbol] = handler; if (isArray(events)) { for (const eventName of events) { if (isString(eventName) && eventName !== "") { source.addEventListener(eventName, handler); } } } this[boundSymbol] = true; if (this.getOption("syncOnInit") === true) { queueMicrotask(() => { syncTarget.call(this); }); } } function unbindSelects() { if (this[observerSymbol]) { this[observerSymbol].disconnect(); delete this[observerSymbol]; } const source = this[sourceElementSymbol]; const handler = this[handlerSymbol]; const events = this.getOption("events"); if (source && handler && isArray(events)) { for (const eventName of events) { if (isString(eventName) && eventName !== "") { source.removeEventListener(eventName, handler); } } } delete this[sourceElementSymbol]; delete this[targetElementSymbol]; delete this[handlerSymbol]; delete this[boundSymbol]; } function syncTarget() { const source = this[sourceElementSymbol]; const target = this[targetElementSymbol]; const param = this.getOption("param"); if (!(source && target)) { return; } if (!isString(param) || param.trim() === "") { addErrorAttribute(this, "Missing param option."); return; } let value = source.value; if (isArray(value)) { value = value.join(","); } const isEmpty = value === null || value === undefined || value === ""; const emptyValue = this.getOption("emptyValue"); const autoFetch = this.getOption("autoFetch") === true; const autoFetchOnEmpty = this.getOption("autoFetchOnEmpty") === true; const disableTarget = this.getOption("disableTarget") === true; const debug = this.getOption("debug") === true; if (debug) { console.log("[select-link]", { source: source.id || source.tagName, target: target.id || target.tagName, param, value, isEmpty, disableTarget, autoFetch, autoFetchOnEmpty, }); } if (isEmpty) { if (disableTarget === true) { target.setAttribute("disabled", ""); if (debug) { console.log("[select-link] target disabled (empty)"); } } if (this.getOption("clearSelection") === true) { target.setOption("selection", []); } if (this.getOption("clearOptions") === true) { target.setOption("options", []); } if (this.getOption("clearTotalMessage") === true) { target.setOption("messages.total", ""); } const emptyMessage = this.getOption("emptyMessage"); if (isString(emptyMessage) && emptyMessage !== "") { target.setOption("messages.control", emptyMessage); } const params = Object.assign({}, target.getOption("filter.params", {})); params[param] = emptyValue; target.setOption("filter.params", params); if (autoFetchOnEmpty && typeof target.fetch === "function") { target.fetch().catch((e) => { addErrorAttribute(target, e); }); } return; } if (disableTarget === true) { target.removeAttribute("disabled"); if (debug) { console.log("[select-link] target enabled (value)"); } } if (this.getOption("clearMessageOnValue") === true) { target.setOption("messages.control", ""); } const params = Object.assign({}, target.getOption("filter.params", {})); params[param] = value; target.setOption("filter.params", params); if (autoFetch && typeof target.fetch === "function") { target.fetch().catch((e) => { addErrorAttribute(target, e); }); } } registerCustomElement(SelectLink);