@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
694 lines (693 loc) • 19.8 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 { buildTree } from "../../data/buildtree.mjs";
// import { Datasource } from "../../data/datasource.mjs";
// import { addAttributeToken } from "../../dom/attributes.mjs";
// import {
// ATTRIBUTE_DISABLED,
// ATTRIBUTE_ERRORMESSAGE,
// ATTRIBUTE_ROLE,
// ATTRIBUTE_UPDATER_INSERT_REFERENCE,
// } from "../../dom/constants.mjs";
// import {
// assembleMethodSymbol,
// CustomElement,
// initMethodSymbol,
// registerCustomElement,
// } from "../../dom/customelement.mjs";
// import { findTargetElementFromEvent } from "../../dom/events.mjs";
// import { findElementWithSelectorUpwards } from "../../dom/util.mjs";
// import { Formatter } from "../../text/formatter.mjs";
// import { isObject, isString } from "../../types/is.mjs";
// import { Node } from "../../types/node.mjs";
// import { NodeRecursiveIterator } from "../../types/noderecursiveiterator.mjs";
// import { Observer } from "../../types/observer.mjs";
// import { ProxyObserver } from "../../types/proxyobserver.mjs";
// import { validateInstance } from "../../types/validate.mjs";
// import {
// datasourceLinkedElementSymbol,
// handleDataSourceChanges,
// } from "../datatable/util.mjs";
// import { ATTRIBUTE_INTEND } from "./../constants.mjs";
// import { CommonStyleSheet } from "../stylesheet/common.mjs";
// import { TreeMenuStyleSheet } from "./stylesheet/tree-menu.mjs";
//
// export { TreeMenu };
//
// /**
// * @private
// * @type {symbol}
// */
// const internalNodesSymbol = Symbol("internalNodes");
//
// /**
// * @private
// * @type {symbol}
// */
// const controlElementSymbol = Symbol("controlElement");
//
// /**
// * @private
// * @type {symbol}
// */
// const openEntryEventHandlerSymbol = Symbol("openEntryEventHandler");
//
// /**
// * @private
// * @type {symbol}
// */
// const dragstartEventHandlerSymbol = Symbol("dragstartEventHandler");
// /**
// * @private
// * @type {symbol}
// */
// const dragenterEventHandlerSymbol = Symbol("dragenterEventHandler");
//
// /**
// * @private
// * @type {symbol}
// */
// const dragleaveEventHandlerSymbol = Symbol("dragleaveEventHandler");
//
// /**
// * @private
// * @type {symbol}
// */
// const dragEventHandlerSymbol = Symbol("dragEventHandler");
//
// /**
// * @private
// * @type {symbol}
// */
// const dragoverEventHandlerSymbol = Symbol("dragoverEventHandler");
//
// /**
// * @private
// * @type {symbol}
// */
// const dropEventHandlerSymbol = Symbol("dropEventHandlerSymbol");
//
// /**
// * TreeMenu
// *
// * <img src="./images/tree-menu.png">
// *
// * You can create this control either by specifying the HTML tag `<monster-tree-menu />` directly in the HTML
// *
// * ```html
// * <monster-tree-menu></monster-tree-menu>
// * ```
// *
// * or using Javascript via the `document.createElement('monster-tree-menu');` method.
// *
// * ```javascript
// * import {TreeMenu} from 'https://cdn.jsdelivr.net/npm/@schukai/component-treemenu@0.1.0/dist/modules/treemenu.js';
// * document.createElement('monster-treemenu');
// * ```
// *
// * @startuml tree-menu.png
// * skinparam monochrome true
// * skinparam shadowing false
// * HTMLElement <|-- CustomElement
// * CustomElement <|-- CustomControl
// * CustomControl <|-- TreeMenu
// * @enduml
// * @since 1.0.0
// * @copyright Volker Schukai
// * @memberOf Monster.Components.TreeMenu
// * @summary A TreeMenu control
// * @fires monster-fetched
// */
// class TreeMenu extends CustomElement {
// /**
// * This method is called internal and should not be called directly.
// *
// * The defaults can be set either directly in the object or via an attribute in the HTML tag.
// * The value of the attribute `data-monster-options` in the HTML tag must be a JSON string.
// *
// * ```
// * <monster-treemenu data-monster-options="{}"></monster-treemenu>
// * ```
// *
// * Since 1.18.0 the JSON can be specified as a DataURI.
// *
// * ```
// * new Monster.Types.DataUrl(btoa(JSON.stringify({
// * shadowMode: 'open',
// * })),'application/json',true).toString()
// * ```
// * @property {Object} toggleEventType=click,touch List of event types to be observed for opening the dropdown
// * @property {Object} templates Template definitions
// * @property {string} templates.main Main template
// * @property {Datasource} datasource data source
// * @property {Object} mapping
// * @property {String} mapping.selector=* Path to select the appropriate entries
// * @property {String} mapping.labelTemplate="" template with the label placeholders in the form ${name}, where name is the key
// * @property {String} mapping.keyTemplate="" template with the key placeholders in the form ${name}, where name is the key
// * @property {String} mapping.rootReferences=['0', undefined, null]
// * @property {String} mapping.idTemplate=id
// * @property {String} mapping.parentTemplate=parent
// * @property {String} mapping.selection
// */
// get defaults() {
// return Object.assign(
// {},
// super.defaults,
// {
// toggleEventType: ["click", "touch"],
// mapping: {
// rootReferences: ["0", undefined, null],
// idTemplate: "id",
// parentTemplate: "parent",
// selector: "*",
// labelTemplate: "",
// valueTemplate: "",
// filter: undefined,
// },
// templates: {
// main: getTemplate(),
// },
//
// datasource: {
// selector: null,
// },
//
// data: [],
// },
// initOptionsFromArguments.call(this),
// );
// }
//
// /**
// * 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_FORM_URL);
// return list;
// }
//
// /**
// *
// */
// [initMethodSymbol]() {
// super[initMethodSymbol]();
// }
//
// /**
// *
// * @return {Monster.Components.TreeMenu.Form}
// */
// [assembleMethodSymbol]() {
// super[assembleMethodSymbol]();
//
// initControlReferences.call(this);
// initEventHandler.call(this);
// // importEntriesFromDatasource.call(this);
// initObserver.call(this);
//
// return this;
// }
//
// /**
// * This method is called internal and should not be called directly.
// *
// * @return {CSSStyleSheet[]}
// */
// static getCSSStyleSheet() {
// return [CommonStyleSheet, TreeMenuStyleSheet];
// }
//
// /**
// * This method is called internal and should not be called directly.
// *
// * @return {string}
// */
// static getTag() {
// return "monster-tree-menu";
// }
// }
//
// /**
// * @private
// */
// function initEventHandler() {
// switchToConfig.call(this);
//
// const selector = this.getOption("datasource.selector");
//
// if (isString(selector)) {
// const element = findElementWithSelectorUpwards(this, selector);
// if (element === null) {
// throw new Error("the selector must match exactly one element");
// }
//
// if (!(element instanceof Datasource)) {
// throw new TypeError("the element must be a datasource");
// }
//
// this[datasourceLinkedElementSymbol] = element;
// element.datasource.attachObserver(
// new Observer(handleDataSourceChanges.bind(this)),
// );
// }
//
// this[openEntryEventHandlerSymbol] = (event) => {
// const container = findTargetElementFromEvent(
// event,
// ATTRIBUTE_ROLE,
// "entry",
// );
// if (!(container instanceof HTMLElement)) {
// return;
// }
//
// //let container = findClosestByAttribute(element, ATTRIBUTE_ROLE, 'option');
// const index = container
// .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
// .split("-")
// .pop();
//
// const currentState = this.getOption("data." + index + ".state");
//
// const newState = currentState === "close" ? "open" : "close";
// this.setOption("data." + index + ".state", newState);
//
// const newVisibility = newState === "open" ? "visible" : "hidden";
//
// if (container.hasAttribute(ATTRIBUTE_INTEND)) {
// const intend = container.getAttribute(ATTRIBUTE_INTEND);
//
// let ref = container.nextElementSibling;
// const childIntend = parseInt(intend) + 1;
//
// const cmp = (a, b) => {
// if (newState === "open") {
// return a === b;
// }
//
// return a >= b;
// };
//
// while (
// ref &&
// ref.hasAttribute(ATTRIBUTE_INTEND) &&
// cmp(parseInt(ref.getAttribute(ATTRIBUTE_INTEND)), childIntend)
// ) {
// const refIndex = ref
// .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
// .split("-")
// .pop();
// this.setOption("data." + refIndex + ".visibility", newVisibility);
//
// if (newState === "close") {
// this.setOption("data." + refIndex + ".state", "close");
// }
//
// ref = ref.nextElementSibling;
// }
// }
// };
//
// const types = this.getOption("toggleEventType", ["click"]);
// for (const [, type] of Object.entries(types)) {
// this.shadowRoot.addEventListener(type, this[openEntryEventHandlerSymbol]);
// }
//
// // for (const [, type] of Object.entries(types)) {
// //
// // self[controlElementSymbol].addEventListener(type, function (event) {
// //
// // const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, 'entry');
// // if (!(element instanceof HTMLElement)) {
// // return;
// // }
// //
// // toggle.call(self);
// //
// //
// // })
// //
// // }
//
// return this;
// }
//
// /**
// * @private
// * @this Form
// */
// function initObserver() {}
//
// /**
// * Import Menu Entries from dataset
// *
// * @since 1.0.0
// * @param {array|object|Map|Set} data
// * @return {TreeMenu}
// * @throws {Error} map is not iterable
// * @private
// */
// function importEntries(data) {
// this[internalNodesSymbol] = new Map();
//
// const mappingOptions = this.getOption("mapping", {});
//
// const filter = mappingOptions?.["filter"];
// const rootReferences = mappingOptions?.["rootReferences"];
//
// const id = this.getOption("mapping.idTemplate", "id");
// const parentID = this.getOption("mapping.parentTemplate", "parent");
//
// const selector = mappingOptions?.["selector"];
//
// const nodes = buildTree(data, selector, id, parentID, {
// filter,
// rootReferences,
// });
//
// const options = [];
// for (const node of nodes) {
// const iterator = new NodeRecursiveIterator(node);
// for (const n of iterator) {
// const formattedValues = formatKeyLabel.call(this, n);
//
// const label = formattedValues.label;
// const value = formattedValues.value;
// const intend = n.level;
//
// const visibility = intend > 0 ? "hidden" : "visible";
// const state = "close";
//
// this[internalNodesSymbol].set(value, n);
//
// options.push({
// value,
// label,
// intend,
// state,
// visibility,
// ["has-children"]: n.hasChildNodes(),
// });
// }
// }
//
// this.setOption("entries", options);
// return this;
// }
//
// /**
// * @private
// */
// function importEntriesFromDatasource() {
// const self = this;
// self.setAttribute(ATTRIBUTE_DISABLED, ATTRIBUTE_DISABLED);
//
// const datasource = self.getOption("datasource");
// if (!(datasource instanceof Datasource)) {
// addAttributeToken(
// self,
// ATTRIBUTE_ERRORMESSAGE,
// "datasource is not defined",
// );
// return;
// }
//
// datasource.attachObserver(
// new Observer(function () {
// if (isObject(this) && this instanceof ProxyObserver) {
// importEntries.call(self, datasource.get());
// }
// }),
// );
//
// datasource
// .read()
// .then(() => {
// new Processing(() => {
// self.removeAttribute(ATTRIBUTE_DISABLED);
// }).run();
// })
// .catch((e) => {
// addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString());
// });
//
// return self;
// }
//
// /**
// *
// * @param {Node} node
// * @return {array<label, value>}
// * @memberOf Monster.Components.TreeMenu
// * @private
// */
// function formatKeyLabel(node) {
// validateInstance(node, Node);
//
// const label = new Formatter(node.value).format(
// this.getOption("mapping.labelTemplate", ""),
// );
// const value = new Formatter(node.value).format(
// this.getOption("mapping.valueTemplate", ""),
// );
//
// return {
// value,
// label,
// };
// }
//
// /**
// * @private
// * @return {Monster.Components.TreeMenu.Form}
// */
// function initControlReferences() {
// if (!this.shadowRoot) {
// throw new Error("no shadow-root is defined");
// }
//
// this[controlElementSymbol] = this.shadowRoot.querySelector(
// "[data-monster-role=control]",
// );
//
// return this;
// }
//
// /**
// *
// * ```
// * <monster-tree-menu data-monster-url="https://example.com/"></monster-tree-menu>
// * ```
//
// * @private
// * @return {object}
// */
// function initOptionsFromArguments() {
// const options = {};
//
// // let url = self.getAttribute(ATTRIBUTE_FORM_URL);
// //
// // if (isString(url)) {
// // options['url'] = new URL(url, document.location).toString()
// // }
//
// return options;
// }
//
// function switchToConfig() {
// if (!this.shadowRoot) {
// throw new Error("no shadow-root is defined");
// }
//
// this[dragoverEventHandlerSymbol] = (event) => {
// const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "entry");
// event.preventDefault();
// if (!(element instanceof HTMLElement)) {
// return;
// }
//
// const dropzone = document.createElement("div");
// dropzone.classList.add("dropzone");
//
// element.prepend(dropzone);
//
// //console.log("over", element.outerHTML, event);
//
// event.dataTransfer.dropEffect = "move";
// };
//
// this[dragenterEventHandlerSymbol] = (event) => {
// const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "entry");
// //console.log("enter", element.outerHTML, event);
//
// event.dataTransfer.dropEffect = "move";
// event.preventDefault();
// };
//
// this[dragleaveEventHandlerSymbol] = (event) => {
// const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "entry");
//
// event.preventDefault();
// if (!(element instanceof HTMLElement)) {
// return;
// }
//
// //console.log("leave", element.outerHTML, event);
//
// event.dataTransfer.dropEffect = "move";
// event.preventDefault();
// };
//
// this[dragEventHandlerSymbol] = (event) => {
// event.preventDefault();
// };
//
// this[dropEventHandlerSymbol] = (event) => {
// const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "entry");
// //console.log("drop", element.outerHTML, event);
// event.preventDefault();
// };
//
// this[dragstartEventHandlerSymbol] = (event) => {
// const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "entry");
// if (!(element instanceof HTMLElement)) {
// return;
// }
//
// //let container = findClosestByAttribute(element, ATTRIBUTE_ROLE, 'option');
// const index = element
// .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
// .split("-")
// .pop();
//
// const currentState = this.getOption("entries." + index + ".state");
// event.dataTransfer.setData("text/plain", "22");
// event.dataTransfer.setData("text/html", "22");
// event.dataTransfer.effectAllowed = "move";
// };
//
// this[controlElementSymbol].addEventListener(
// "dragstart",
// this[dragstartEventHandlerSymbol],
// );
// this[controlElementSymbol].addEventListener(
// "dragenter",
// this[dragenterEventHandlerSymbol],
// );
// this[controlElementSymbol].addEventListener(
// "dragleave",
// this[dragleaveEventHandlerSymbol],
// );
// this[controlElementSymbol].addEventListener(
// "dragover",
// this[dragoverEventHandlerSymbol],
// );
// this[controlElementSymbol].addEventListener(
// "drop",
// this[dropEventHandlerSymbol],
// );
// }
//
// // /**
// // * @private
// // * @throws {Error} missing default slot
// // * @throws {Error} no shadow-root is defined
// // * @throws {Error} missing url
// // * @throws {Error} we won't be able to read the data
// // * @throws {Error} request failed
// // * @throws {Error} not found
// // * @throws {Error} undefined status or type
// // * @fires monster-fetched
// // */
// // function initIntersectionObserver() {
// // const self = this;
// //
// // if (self[intersectionObserverWasInitialized] === true) {
// // return
// // }
// //
// // self[intersectionObserverWasInitialized] = true;
// //
// // let options = {
// // threshold: [0.5]
// // }
// //
// // const callback = (entries, observer) => {
// //
// // for (const [, entry] of entries.entries()) {
// // if (entry.isIntersecting === true) {
// // if (!self.hasAttribute(ATTRIBUTE_FORM_RELOAD) || self.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase() === 'onshow') {
// // observer.disconnect();
// // }
// //
// // try {
// // loadContent.call(self);
// // } catch (e) {
// // self.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
// // }
// //
// //
// // }
// // }
// // }
// //
// // const observer = new IntersectionObserver(callback, options);
// // observer.observe(self);
// //
// //
// // }
//
// /**
// * @private
// * @return {string}
// */
// function getTemplate() {
// // language=HTML
// return `
// <template id="entries">
// <div data-monster-role="entry"
// draggable="true"
// data-monster-attributes="
// data-monster-intend path:entries.intend,
// data-monster-state path:entries.state,
// data-monster-visibility path:entries.visibility,
// data-monster-filtered path:entries.filtered,
// data-monster-has-children path:entries.has-children">
//
// <button data-monster-role="button"
// data-monster-attributes="
// type path:type,
// role path:role,
// value path:entries.value,
// name path:name,
// part path:type | prefix:option- | suffix: form" tabindex="-1">
// <span data-monster-role="folder-handler"></span>
// <span data-monster-replace="path:entries | index:label" part="entry-label"></span>
// </button>
// </template>
//
// <div data-monster-role="control" part="control">
// <div part="entries" data-monster-role="entries"
// data-monster-insert="entries path:entries"
// tabindex="-1"></div>
// </div>
// `;
// }
//
// registerCustomElement(TreeMenu);