Skip to content
Snippets Groups Projects
Verified Commit e5e74883 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

feat: optimize tree menu #191

parent 08bdc580
No related branches found
No related tags found
No related merge requests found
Showing
with 2002 additions and 800 deletions
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>optimize tree-menu #191</title>
<script src="./191.mjs" type="module"></script>
</head>
<body>
<h1>optimize tree-menu #191</h1>
<p></p>
<ul>
<li class="monster-theme-primary-1"><a
href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/191">Issue #191</a></li>
<li><a href="/">Back to overview</a></li>
</ul>
<main>
<monster-datasource-rest id="menu-rest"
data-monster-option-read-path="dataset"
data-monster-option-features-autoinit="true"
data-monster-option-read-url="/issue-191.json"
>
</monster-datasource-rest>
<monster-datasource-dom id="menu-dom">
<script type="application/json">
{
"1": {
"id": "1",
"title": "Test 1",
"address": "Here 1",
"parent": "2"
},
"2": {
"id": "2",
"title": "Test 2",
"address": "Here 2",
"parent": "0"
},
"3": {
"id": "3",
"title": "Test 3",
"address": "Here 3",
"parent": "2"
},
"4": {
"id": "4",
"title": "Test 4",
"address": "Here 4",
"parent": "0"
},
"5": {
"id": "5",
"title": "Test 5",
"address": "Here 5",
"parent": "0"
},
"6": {
"id": "6",
"title": "Test 6",
"address": "Here 6",
"parent": "4"
},
"7": {
"id": "7",
"title": "Test 7",
"address": "Here 7",
"parent": "1"
}
}
</script>
</monster-datasource-dom>
<monster-panel>
<monster-split-panel>
<monster-panel id="inner2" data-monster-option-heightadjustment="5"
slot="start">
<monster-tree-menu
data-monster-option-mapping-idtemplate="id"
data-monster-option-mapping-parentkey="parent_id"
data-monster-option-mapping-labeltemplate="${title}"
data-monster-option-mapping-valuetemplate="${path:url | tostring}"
data-monster-option-mapping-icontemplate="${path:icon | default:default}"
data-monster-option-datasource-selector="#menu-rest"
value=""
>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="box" viewBox="0 0 16 16">
<path d="M6 9a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3A.5.5 0 0 1 6 9M3.854 4.146a.5.5 0 1 0-.708.708L4.793 6.5 3.146 8.146a.5.5 0 1 0 .708.708l2-2a.5.5 0 0 0 0-.708z"/>
<path d="M2 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2zm12 1a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/>
</symbol>
<symbol id="default" viewBox="0 0 16 16">
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4z"/>
</symbol>
<symbol id="balloon" viewBox="0 0 16 16">
<path d="M8 9.984C10.403 9.506 12 7.48 12 5a4 4 0 0 0-8 0c0 2.48 1.597 4.506 4 4.984M13 5c0 2.837-1.789 5.227-4.52 5.901l.244.487a.25.25 0 1 1-.448.224l-.008-.017c.008.11.02.202.037.29.054.27.161.488.419 1.003.288.578.235 1.15.076 1.629-.157.469-.422.867-.588 1.115l-.004.007a.25.25 0 1 1-.416-.278c.168-.252.4-.6.533-1.003.133-.396.163-.824-.049-1.246l-.013-.028c-.24-.48-.38-.758-.448-1.102a3 3 0 0 1-.052-.45l-.04.08a.25.25 0 1 1-.447-.224l.244-.487C4.789 10.227 3 7.837 3 5a5 5 0 0 1 10 0m-6.938-.495a2 2 0 0 1 1.443-1.443C7.773 2.994 8 2.776 8 2.5s-.226-.504-.498-.459a3 3 0 0 0-2.46 2.461c-.046.272.182.498.458.498s.494-.227.562-.495"/>
</symbol>
<symbol id="cart" viewBox="0 0 16 16">
<path d="M8 1a2.5 2.5 0 0 1 2.5 2.5V4h-5v-.5A2.5 2.5 0 0 1 8 1m3.5 3v-.5a3.5 3.5 0 1 0-7 0V4H1v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4zM2 5h12v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z"/>
</symbol>
<symbol id="box2" viewBox="0 0 16 16">
<path d="M8 7.982C9.664 6.309 13.825 9.236 8 13 2.175 9.236 6.336 6.31 8 7.982"/>
<path d="M3.75 0a1 1 0 0 0-.8.4L.1 4.2a.5.5 0 0 0-.1.3V15a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V4.5a.5.5 0 0 0-.1-.3L13.05.4a1 1 0 0 0-.8-.4zm0 1H7.5v3h-6zM8.5 4V1h3.75l2.25 3zM15 5v10H1V5z"/>
</symbol>
</svg>
</monster-tree-menu>
</monster-panel>
</monster-split-panel>
</monster-panel>
</main>
</body>
</html>
/**
* @file development/issues/open/191.mjs
* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/191
* @description optimize tree-menu
* @issue 191
*/
import "../../../source/components/style/property.pcss";
import "../../../source/components/style/color.pcss";
import "../../../source/components/style/link.pcss";
import "../../../source/components/style/normalize.pcss";
import "../../../source/components/style/typography.pcss";
import "../../../source/components/tree-menu/tree-menu.mjs";
import "../../../source/components/layout/split-panel.mjs";
import "../../../source/components/layout/panel.mjs";
import "../../../source/components/datatable/datasource/dom.mjs";
import "../../../source/components/datatable/datasource/rest.mjs";
setTimeout(() => {
const obj = document.querySelector("monster-tree-menu");
obj.selectEntry("/alvine/?cmd=9130")
setTimeout(() => {
//obj.selectEntry("/ebay")
obj.selectEntry("/alvine/?cmd=8157")
}, 1000)
}, 1000)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>optimize tree-menu #191</title>
<script src="./191.mjs" type="module"></script>
</head>
<body>
<h1>optimize tree-menu #191</h1>
<p></p>
<ul>
<li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/191">Issue #191</a></li>
<li><a href="/">Back to overview</a></li>
</ul>
<main>
<monster-tree-menu>
<script type="application/json">
{
"data": [
{
"id": 1,
"name": "Item 1",
"children": [
{
"id": 2,
"name": "Item 1.1",
"children": [
{
"id": 3,
"name": "Item 1.1.1"
},
{
"id": 4,
"name": "Item 1.1.2"
}
]
},
{
"id": 5,
"name": "Item 1.2",
"children": [
{
"id": 6,
"name": "Item 1.2.1"
},
{
"id": 7,
"name": "Item 1.2.2"
}
]
}
]
},
{
"id": 8,
"name": "Item 2",
"children": [
{
"id": 9,
"name": "Item 2.1",
"children": [
{
"id": 10,
"name": "Item 2.1.1"
},
{
"id": 11,
"name": "Item 2.1.2"
}
]
},
{
"id": 12,
"name": "Item 2.2",
"children": [
{
"id": 13,
"name": "Item 2.2.1"
},
{
"id": 14,
"name": "Item 2.2.2"
}
]
}
]
}
]
}
</script>
</monster-tree-menu>
</main>
</body>
</html>
/**
* @file development/issues/open/191.mjs
* @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/191
* @description optimize tree-menu
* @issue 191
*/
import "../../../source/components/style/property.pcss";
import "../../../source/components/style/normalize.pcss";
import "../../../source/components/style/typography.pcss";
import "../../../source/components/tree-menu/tree-menu.mjs";
const json =
`{
"dataset": [
{
"id": 308004,
"parent_id": 0,
"title": "Bestellungen",
"url": "/apps/commerce/order-list",
"weight": 2,
"icon": "cart",
"css_classes": ""
},
{
"id": 108003,
"parent_id": 308004,
"title": "Bestellung suchen",
"url": "/alvine/?cmd=8003",
"weight": 2,
"icon": "box2",
"css_classes": ""
},
{
"id": 108004,
"parent_id": 308004,
"title": "Bestellungen (Legacy)",
"url": "/alvine/?cmd=8004",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 109700,
"parent_id": 308004,
"title": "Visuelle Überprüfung",
"url": "/alvine/?cmd=9700",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 108007,
"parent_id": 308004,
"title": "Wiedervorlagen",
"url": "/alvine/?cmd=8007",
"weight": 3,
"icon": null,
"css_classes": ""
},
{
"id": 108073,
"parent_id": 308004,
"title": "Zahlungseingang",
"url": "/alvine/?cmd=8073",
"weight": 4,
"icon": null,
"css_classes": ""
},
{
"id": 114000,
"parent_id": 308004,
"title": "Angebote",
"url": "/alvine/?cmd=14000",
"weight": 5,
"icon": null,
"css_classes": ""
},
{
"id": 131600,
"parent_id": 308004,
"title": "Bestelleingangsregeln",
"url": "/alvine/?cmd=31600",
"weight": 6,
"icon": null,
"css_classes": ""
},
{
"id": 300125,
"parent_id": 0,
"title": "Kunden",
"url": "/apps/commerce/user-list",
"weight": 3,
"icon": "balloon",
"css_classes": ""
},
{
"id": 100220,
"parent_id": 300125,
"title": "Tickets",
"url": "/alvine/?cmd=220",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 100125,
"parent_id": 300125,
"title": "Kunden (Legacy)",
"url": "/alvine/?cmd=125",
"weight": 3,
"icon": null,
"css_classes": ""
},
{
"id": 124013,
"parent_id": 300125,
"title": "Kundensperrliste",
"url": "/alvine/?cmd=24013",
"weight": 3,
"icon": null,
"css_classes": ""
},
{
"id": 100117,
"parent_id": 300125,
"title": "Gruppen",
"url": "/alvine/?cmd=117",
"weight": 4,
"icon": null,
"css_classes": ""
},
{
"id": 126038,
"parent_id": 300125,
"title": "Kundenbewertungen",
"url": "/alvine/?cmd=26038",
"weight": 5,
"icon": null,
"css_classes": ""
},
{
"id": 108181,
"parent_id": 300125,
"title": "Merklisten",
"url": "/alvine/?cmd=8181",
"weight": 6,
"icon": null,
"css_classes": ""
},
{
"id": 209231,
"parent_id": 0,
"title": "Marketing",
"url": "/alvine/?cmd=209231",
"weight": 4,
"icon": null,
"css_classes": ""
},
{
"id": 109231,
"parent_id": 209231,
"title": "Partnerverwaltung",
"url": "/alvine/?cmd=9231",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 122002,
"parent_id": 209231,
"title": "Gutscheine",
"url": "/alvine/?cmd=22002",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 100600,
"parent_id": 209231,
"title": "Serienbrief",
"url": "/alvine/?cmd=600",
"weight": 3,
"icon": null,
"css_classes": ""
},
{
"id": 100320,
"parent_id": 209231,
"title": "E-Mail-Sperrliste",
"url": "/alvine/?cmd=320",
"weight": 4,
"icon": null,
"css_classes": ""
},
{
"id": 150040,
"parent_id": 209231,
"title": "Kampagnen",
"url": "/alvine/?cmd=50040",
"weight": 5,
"icon": null,
"css_classes": ""
},
{
"id": 100330,
"parent_id": 209231,
"title": "Marketingadressen",
"url": "/alvine/?cmd=330",
"weight": 6,
"icon": null,
"css_classes": ""
},
{
"id": 200001,
"parent_id": 0,
"title": "Vertriebskanäle",
"url": "/",
"weight": 5,
"icon": null,
"css_classes": ""
},
{
"id": 100001,
"parent_id": 200001,
"title": "eBay",
"url": "/ebay",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 100002,
"parent_id": 200001,
"title": "Amazon",
"url": "/",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 201023,
"parent_id": 0,
"title": "Produkte",
"url": "/",
"weight": 6,
"icon": null,
"css_classes": ""
},
{
"id": 101023,
"parent_id": 201023,
"title": "Produkte (Legacy)",
"url": "/alvine/?cmd=1023",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 208124,
"parent_id": 201023,
"title": "Katalog",
"url": "/",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 108113,
"parent_id": 208124,
"title": "Marken",
"url": "/alvine/?cmd=8113",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 109010,
"parent_id": 208124,
"title": "Abteilungen",
"url": "/alvine/?cmd=9010",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 108124,
"parent_id": 208124,
"title": "Farben",
"url": "/alvine/?cmd=8124",
"weight": 3,
"icon": null,
"css_classes": ""
},
{
"id": 109119,
"parent_id": 208124,
"title": "Raster",
"url": "/alvine/?cmd=9119",
"weight": 4,
"icon": null,
"css_classes": ""
},
{
"id": 109081,
"parent_id": 208124,
"title": "Ausführung",
"url": "/alvine/?cmd=9081",
"weight": 5,
"icon": null,
"css_classes": ""
},
{
"id": 109130,
"parent_id": 208124,
"title": "Material",
"url": "/alvine/?cmd=9130",
"weight": 6,
"icon": null,
"css_classes": ""
},
{
"id": 108053,
"parent_id": 208124,
"title": "Kollektion",
"url": "/alvine/?cmd=8053",
"weight": 7,
"icon": null,
"css_classes": ""
},
{
"id": 126013,
"parent_id": 208124,
"title": "Saison",
"url": "/alvine/?cmd=26013",
"weight": 8,
"icon": null,
"css_classes": ""
},
{
"id": 109258,
"parent_id": 208124,
"title": "Zuweisung 1",
"url": "/alvine/?cmd=9258",
"weight": 9,
"icon": null,
"css_classes": ""
},
{
"id": 109269,
"parent_id": 208124,
"title": "Zuweisung 2",
"url": "/alvine/?cmd=9269",
"weight": 10,
"icon": null,
"css_classes": ""
},
{
"id": 109310,
"parent_id": 208124,
"title": "Zuweisung 3",
"url": "/alvine/?cmd=9310",
"weight": 10,
"icon": null,
"css_classes": ""
},
{
"id": 131531,
"parent_id": 201023,
"title": "Lagerliste",
"url": "/alvine/?cmd=31531",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 108157,
"parent_id": 201023,
"title": "Stückliste Liste",
"url": "/alvine/?cmd=8157",
"weight": 4,
"icon": null,
"css_classes": ""
},
{
"id": 109025,
"parent_id": 201023,
"title": "Varianten",
"url": "/alvine/?cmd=9025",
"weight": 5,
"icon": null,
"css_classes": ""
},
{
"id": 109058,
"parent_id": 201023,
"title": "Kategorien",
"url": "/alvine/?cmd=9058",
"weight": 7,
"icon": null,
"css_classes": ""
},
{
"id": 126035,
"parent_id": 201023,
"title": "Massenbearbeitung",
"url": "/alvine/?cmd=26035",
"weight": 8,
"icon": null,
"css_classes": ""
},
{
"id": 110000,
"parent_id": 201023,
"title": "Konditionsübersicht",
"url": "/alvine/?cmd=10000",
"weight": 9,
"icon": null,
"css_classes": ""
},
{
"id": 209211,
"parent_id": 0,
"title": "Wareneingang",
"url": "/",
"weight": 8,
"icon": null,
"css_classes": ""
},
{
"id": 109211,
"parent_id": 209211,
"title": "Avise",
"url": "/alvine/?cmd=9211",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 209221,
"parent_id": 209211,
"title": "Retoure",
"url": "/",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 109221,
"parent_id": 209221,
"title": "Retouren",
"url": "/alvine/?cmd=9221",
"weight": 1,
"icon": null,
"css_classes": ""
},
{
"id": 109037,
"parent_id": 209221,
"title": "Tagesabschluss Retoureneingang",
"url": "/alvine/?cmd=9037",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 108064,
"parent_id": 209211,
"title": "Avis scannen",
"url": "/alvine/?cmd=8064",
"weight": 2,
"icon": null,
"css_classes": ""
},
{
"id": 109201,
"parent_id": 209211,
"title": "Lieferungen",
"url": "/alvine/?cmd=9201",
"weight": 3,
"icon": null,
"css_classes": ""
}
],
"sys": {
"code": 200,
"result": {},
"api_version": "1"
}
}
`;
// check if json is valid
JSON.parse(json)
const requestDelay = 100
export default [
{
url: '/issue-191.json',
method: 'get',
rawResponse: async (req, res) => {
res.setHeader('Content-Type', 'application/json')
res.statusCode = 200
setTimeout(function() {
res.end(json)
}, requestDelay);
},
}
];
\ No newline at end of file
...@@ -9,10 +9,12 @@ ...@@ -9,10 +9,12 @@
background-color: red; background-color: red;
} }
:host { :host {
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
width: 100%;
margin: 0;
padding: 0;
outline: none;
} }
This diff is collapsed.
/**
* Copyright © schukai GmbH 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 schukai GmbH.
*
* 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 schukai GmbH
* @memberOf Monster.Components.TreeMenu
* @summary A TreeMenu control
* @fires Monster.Components.TreeMenu.event: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.Components.TreeMenu.event: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);
@import "../../style/display.pcss";
@import "../../style/border.pcss";
@import "../../style/color.pcss";
@import "../../style/theme.pcss";
@import "../../style/form.pcss";
@import "../../style/typography.pcss";
@import "../../style/control.pcss";
:host { :host {
border: 1px solid #08e808;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
} }
[data-monster-role=entry].selected:not([data-monster-state=open]) {
background-color: var(--monster-bg-color-secondary-2);
}
[data-monster-role=entry]:hover {
background-color: var(--monster-bg-color-primary-2);
}
[data-monster-role=control] { [data-monster-role=control] {
overflow-x: hidden; overflow-x: hidden;
...@@ -15,19 +30,6 @@ ...@@ -15,19 +30,6 @@
scrollbar-color: transparent transparent; scrollbar-color: transparent transparent;
} }
::-webkit-scrollbar {
width: 5px;
height: 10px;
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.35);
}
::-webkit-scrollbar-track {
background: transparent;
}
[data-monster-role="button"] { [data-monster-role="button"] {
width: 100%; width: 100%;
border: 0; border: 0;
...@@ -35,73 +37,98 @@ ...@@ -35,73 +37,98 @@
padding: 5px; padding: 5px;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
display: block; display: flex;
overflow: hidden; overflow: hidden;
text-align: left; text-align: left;
flex-direction: row;
flex-wrap: nowrap;
cursor: pointer;
} }
[data-monster-role=entry][data-monster-visibility=hidden] { [data-monster-role=entry][data-monster-visibility=hidden] {
display: none; display: none;
} }
[data-monster-role=entry] .dropzone {
background-color: red;
width: 100%;
height: 20px;
}
[data-monster-role=entry][data-monster-intend="0"] { [data-monster-role=entry][data-monster-intend="0"] {
margin: 0px 0px 0px 0px; margin: 0 0 0 0;
display: flex; display: flex;
font-size: 1rem; font-size: 1rem;
} }
[data-monster-role=entry][data-monster-intend="1"] { [data-monster-role=entry][data-monster-intend="1"] {
margin: 0px 0px 0px 20px; margin: 0 0 0 20px;
} }
[data-monster-role=entry][data-monster-intend="2"] { [data-monster-role=entry][data-monster-intend="2"] {
margin: 0px 0px 0px 40px; margin: 0 0 0 40px;
font-size: 0.75rem; font-size: 0.75rem;
} }
[data-monster-role=entry][data-monster-intend="3"] { [data-monster-role=entry][data-monster-intend="3"] {
margin: 0px 0px 0px 60px; margin: 0 0 0 60px;
font-size: 0.70rem; font-size: 0.70rem;
} }
[data-monster-role=entry][data-monster-intend="4"] { [data-monster-role=entry][data-monster-intend="4"] {
margin: 0px 0px 0px 80px; margin: 0 0 0 80px;
font-size: 0.65rem; font-size: 0.65rem;
} }
[data-monster-role=entry][data-monster-intend="5"] { [data-monster-role=entry][data-monster-intend="5"] {
margin: 0px 0px 0px 100px; margin: 0 0 0 100px;
font-size: 0.65rem; font-size: 0.65rem;
} }
[data-monster-role=entry][data-monster-intend="6"] { [data-monster-role=entry][data-monster-intend="6"] {
margin: 0px 0px 0px 110px; /* smaller intend */ margin: 0 0 0 110px; /* smaller intend */
font-size: 0.65rem; font-size: 0.65rem;
} }
[data-monster-role=entry][data-monster-intend="7"] { [data-monster-role=entry][data-monster-intend="7"] {
margin: 0px 0px 0px 120px; /* smaller intend */ margin: 0 0 0 120px; /* smaller intend */
font-size: 0.65rem; font-size: 0.65rem;
} }
[data-monster-role=entry][data-monster-intend="8"] { [data-monster-role=entry][data-monster-intend="8"] {
margin: 0px 0px 0px 130px; /* smaller intend */ margin: 0 0 0 130px; /* smaller intend */
font-size: 0.65rem; font-size: 0.65rem;
} }
[data-monster-role=status-badges] {
appearance: none;
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' transform='rotate(270 8 8)' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
mask-size: 0.8em;
mask-repeat: no-repeat;
mask-position: center center;
background-color: var(--monster-bg-color-primary-4);
width: 16px;
min-height: 16px;
}
[data-monster-state=open] [data-monster-role=status-badges] {
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
background-color: var(--monster-bg-color-primary-4);
}
[data-monster-has-children=false] [data-monster-role=status-badges] {
mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%2016%2016%22%3E%0A%20%20%3Cpath%20d%3D%22M4%208a.5.5%200%200%201%20.5-.5h7a.5.5%200%200%201%200%201h-7A.5.5%200%200%201%204%208z%22%2F%3E%0A%3C%2Fsvg%3E");
background-color: var(--monster-bg-color-primary-4);
}
[data-monster-role=icon] {
display: flex;
}
[data-monster-role=icon] svg {
fill: var(--monster-bg-color-primary-4);
appearance: none;
width: 16px;
min-height: 16px;
margin-right: 5px;
}
This diff is collapsed.
This diff is collapsed.
...@@ -119,6 +119,7 @@ class RestAPI extends Server { ...@@ -119,6 +119,7 @@ class RestAPI extends Server {
init: { init: {
method: "GET", method: "GET",
}, },
path: null,
responseCallback: undefined, responseCallback: undefined,
acceptedStatus: [200], acceptedStatus: [200],
url: null, url: null,
......
...@@ -100,7 +100,11 @@ function getSlottedElements(query, name) { ...@@ -100,7 +100,11 @@ function getSlottedElements(query, name) {
for (const [, slot] of Object.entries(slots)) { for (const [, slot] of Object.entries(slots)) {
slot.assignedElements().forEach(function (node) { slot.assignedElements().forEach(function (node) {
if (!(node instanceof HTMLElement)) return;
if (!(node instanceof HTMLElement)
&& !(node instanceof SVGElement)
&& !(node instanceof MathMLElement)
) return;
if (isString(query)) { if (isString(query)) {
if (query.length > 0) { if (query.length > 0) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment