@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
276 lines (233 loc) • 6.82 kB
JavaScript
/**
* 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);