UNPKG

@schukai/monster

Version:

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

694 lines (693 loc) 19.8 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 { 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);