WIP sl-Tree

This commit is contained in:
Milan 2023-08-23 09:34:00 +02:00
parent b97c58c016
commit 0f1f45a0cc
8 changed files with 413 additions and 0 deletions

View File

@ -0,0 +1,256 @@
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {SlTree} from "@shoelace-style/shoelace";
import {Et2Link} from "../Et2Link/Et2Link";
import {Et2widgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin";
import {et2_no_init} from "../et2_core_common";
import {egw, framework} from "../../jsapi/egw_global";
import {SelectOption, find_select_options, cleanSelectOptions} from "../Et2Select/FindSelectOptions";
import {html, TemplateResult} from "@lion/core";
import {egwIsMobile} from "../../egw_action/egw_action_common";
export type TreeItem = {
child: Boolean | 1,
data?: Object,//{sieve:true,...} or {acl:true} or other
id: String,
im0: String,
im1: String,
im2: String,
item: TreeItem[],
checked?: Boolean,
nocheckbox: number | Boolean,
open: 0 | 1,
parent: String,
text: String,
tooltip: String
}
export class Et2Tree extends Et2widgetWithSelectMixin(SlTree) {
private input: any = null;
private div: JQuery;
private autoloading_url: any;
private selectOptions: TreeItem[];
constructor() {
super();
}
static get properties() {
return {
...super.properties,
multiple: {
name: "",
type: Boolean,
default: false,
description: "Allow selecting multiple options"
},
selectOptions: {
type: "any",
name: "Select options",
default: {},
description: "Used to set the tree options."
},
onClick: {
name: "onClick",
type: "js",
description: "JS code which gets executed when clicks on text of a node"
},
onSelect: {
name: "onSelect",
type: "js",
default: et2_no_init,
description: "Javascript executed when user selects a node"
},
onCheck: {
name: "onCheck",
type: "js",
default: et2_no_init,
description: "Javascript executed when user checks a node"
},
// TODO do this : --> onChange event is mapped depending on multiple to onCheck or onSelect
onOpenStart: {
name: "onOpenStart",
type: "js",
default: et2_no_init,
description: "Javascript function executed when user opens a node: function(_id, _widget, _hasChildren) returning true to allow opening!"
},
onOpenEnd: {
name: "onOpenEnd",
type: "js",
default: et2_no_init,
description: "Javascript function executed when opening a node is finished: function(_id, _widget, _hasChildren)"
},
imagePath: {
name: "Image directory",
type: String,
default: egw().webserverUrl + "/api/templates/default/images/dhtmlxtree/",//TODO we will need a different path here! maybe just rename the path?
description: "Directory for tree structure images, set on server-side to 'dhtmlx' subdir of templates image-directory"
},
value: {
type: "any",
default: {}
},
actions: {
name: "Actions array",
type: "any",
default: et2_no_init,
description: "List of egw actions that can be done on the tree. This includes context menu, drag and drop. TODO: Link to action documentation"
},
autoLoading: {
name: "Auto loading",
type: String,
default: "",
description: "JSON URL or menuaction to be called for nodes marked with child=1, but not having children, GET parameter selected contains node-id"
},
stdImages: {
name: "Standard images",
type: String,
default: "",
description: "comma-separated names of icons for a leaf, closed and opened folder (default: leaf.png,folderClosed.png,folderOpen.png), images with extension get loaded from imagePath, just 'image' or 'appname/image' are allowed too"
},
multiMarking: {
name: "multi marking",
type: "any",
default: false,
description: "Allow marking multiple nodes, default is false which means disabled multiselection, true or 'strict' activates it and 'strict' makes it strict to only same level marking"
},
highlighting: {
name: "highlighting",
type: Boolean,
default: false,
description: "Add highlighting class on hovered over item, highlighting is disabled by default"
},
}
};
public set onOpenStart(_handler: Function) {
this.installHandler("onOpenStart", _handler)
}
public set onChange(_handler: Function) {
this.installHandler("onChange", _handler)
}
public set onClick(_handler: Function) {
this.installHandler("onClick", _handler)
}
public set onSelect(_handler: Function) {
this.installHandler("onSelect", _handler)
}
public set onOpenEnd(_handler: Function) {
this.installHandler("onOpenEnd", _handler)
}
_optionTemplate() {
// @ts-ignore
this.selectOptions= find_select_options(this)[1];
//slot = expanded/collapsed instead of expand/collapse like it is in documentation
let result: TemplateResult<1> = html``
for (const selectOption of this.selectOptions) {
result = html`${result}
<sl-tree-item>
${this.recursivelyAddChildren(selectOption)}
</sl-tree-item>`
}
const h = html`${result}`
return h
}
/**
* @deprecated assign to onOpenStart
* @param _handler
*/
public set_onopenstart(_handler: Function) {
this.installHandler("onOpenStart", _handler)
}
/**
* @deprecated assign to onChange
* @param _handler
*/
public set_onchange(_handler: Function) {
this.installHandler('onchange', _handler);
}
/**
* @deprecated assign to onClick
* @param _handler
*/
public set_onclick(_handler: Function) {
this.installHandler('onclick', _handler);
}
/**
* @deprecated assign to onSelect
* @param _handler
*/
public set_onselect(_handler: Function) {
this.installHandler('onselect', _handler);
}
/**
* @deprecated assign to onOpenEnd
* @param _handler
*/
public set_onopenend(_handler: Function) {
this.installHandler('onOpenEnd', _handler);
}
private recursivelyAddChildren(item: any): TemplateResult<1> {
let img:String =item.im0??item.im1??item.im2;
let attributes = ""
let res: TemplateResult<1> = html`${item.text}`;
if(img){
img = "api/templates/default/images/dhtmlxtree/"+img
//sl-icon images need to be svgs if there is a png try to find the corresponding svg
if(img.endsWith(".png"))img = img.replace(".png",".svg");
res = html`<sl-icon src=${img}></sl-icon>${res}`
}
if (item.item?.length > 0) // there are children available
{
for (const subItem of item.item) {
res = html`
${res}
<sl-tree-item lazy> ${this.recursivelyAddChildren(subItem)}</sl-tree-item>`
}
// }else if(item.child === 1){
// res = html``
// }
}
return res;
}
private installHandler(_name: String, _handler: Function) {
if (this.input == null) this.createTree(this);
// automatic convert onChange event to oncheck or onSelect depending on multiple is used or not
// if (_name == "onchange") {
// _name = this.options.multiple ? "oncheck" : "onselect"
// }
// let handler = _handler;
// let widget = this;
// this.input.attachEvent(_name, function(_id){
// let args = jQuery.makeArray(arguments);
// // splice in widget as 2. parameter, 1. is new node-id, now 3. is old node id
// args.splice(1, 0, widget);
// // try to close mobile sidemenu after clicking on node
// if (egwIsMobile() && typeof args[2] == 'string') framework.toggleMenu('on');
// return handler.apply(this, args);
// });
}
private createTree(widget: this) {
widget.input = document.querySelector("et2-tree");
// Allow controlling icon size by CSS
widget.input.def_img_x = "";
widget.input.def_img_y = "";
// to allow "," in value, eg. folder-names, IF value is specified as array
widget.input.dlmtr = ':}-*(';
}
}
customElements.define("et2-tree", Et2Tree);
const tree = new Et2Tree();

View File

@ -0,0 +1,12 @@
import {Et2Tree} from "./Et2Tree";
export function initMailTree(): Et2Tree {
const changeFunction = () => {
console.log("change"+tree)
}
const tree: Et2Tree = document.querySelector("et2-tree");
tree.selection = "single";
tree.addEventListener("sl-selection-change", (event)=>{console.log(event)})
return tree;
}

View File

@ -0,0 +1,7 @@
const selectionMode = document.querySelector('#selection-mode');
const tree = document.querySelector('.tree-selectable');
selectionMode.addEventListener('sl-change', () => {
tree.querySelectorAll('sl-tree-item').forEach(item => (item.selected = false));
tree.selection = selectionMode.value;
});

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TestSite</title>
</head>
<body>
<sl-tree class="tree-selectable">
<sl-tree-item>
Item 1
<sl-tree-item>
Item A
<sl-tree-item>Item Z</sl-tree-item>
<sl-tree-item>Item Y</sl-tree-item>
<sl-tree-item>Item X</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item B</sl-tree-item>
<sl-tree-item>Item C</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item 2</sl-tree-item>
<sl-tree-item>Item 3</sl-tree-item>
</sl-tree>
<script src="TreeTest.js"></script>
</body>
</html>

View File

@ -99,6 +99,7 @@ import "./Et2Vfs/Et2VfsMime";
import "./Et2Vfs/Et2VfsUid"; import "./Et2Vfs/Et2VfsUid";
import "./Et2Textbox/Et2Password"; import "./Et2Textbox/Et2Password";
import './Et2Textbox/Et2Searchbox'; import './Et2Textbox/Et2Searchbox';
import "./Et2TreeWidget/Et2Tree";
/* Include all widget classes here, we only care about them registering, not importing anything*/ /* Include all widget classes here, we only care about them registering, not importing anything*/
import './et2_widget_vfs'; // Vfs must be first (before et2_widget_file) due to import cycle import './et2_widget_vfs'; // Vfs must be first (before et2_widget_file) due to import cycle

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg18"
width="32"
height="32"
viewBox="0 0 32 32"
sodipodi:docname="a"
inkscape:version="1.2.2 (732a01da63, 2022-12-09, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs22" />
<sodipodi:namedview
id="namedview20"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="16.8125"
inkscape:cx="6.0966543"
inkscape:cy="16"
inkscape:window-width="3440"
inkscape:window-height="1403"
inkscape:window-x="1366"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g24" />
<g
inkscape:groupmode="layer"
inkscape:label="Image"
id="g24">
<image
width="32"
height="32"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
bWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp
bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6
eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0
NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJo
dHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlw
dGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEu
MC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVz
b3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1N
Ok9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODA4M0U5MTlCN0Qz
RjkyNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCNzQ1MjFDNjdBREUxMUUzQkVENUVDRDUw
NzRFRkVGMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo4MjBGODhFODdBREUxMUUzQkVENUVD
RDUwNzRFRkVGMyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRv
c2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTEwQTA3
RkUyQjIwNjgxMThDMTQ4REE5QUQ0MTFENTQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4
MDExNzQwNzIwNjgxMTgwODNFOTE5QjdEM0Y5MjciLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRm
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
rkJggg==
"
id="image26" />
<path
style="fill:#000000"
d="m 18,28.314451 c 0,-0.377052 0.978441,-1.727052 2.174314,-3 L 22.348628,23 20.174314,20.685549 C 18.059263,18.43418 17.290894,16 18.695281,16 c 0.382405,0 1.901015,1.155166 3.374689,2.567036 l 2.679409,2.567036 2.153991,-2.738356 2.153991,-2.738355 L 30.2,16.8 l 1.142639,1.142639 -2.738355,2.153991 -2.738356,2.153991 2.567036,2.679409 C 29.844834,26.403704 31,27.922314 31,28.304719 c 0,1.404387 -2.43418,0.636018 -4.685549,-1.479033 L 24,24.651372 21.685549,26.825686 C 19.344919,29.024593 18,29.567869 18,28.314451 Z M 4.2,24.8 3,23.6 V 18.3 13 h 7.674314 7.674314 L 16,15.5 13.651372,18 15.97246,20.470685 18.293548,22.94137 17.024329,24.470685 15.755111,26 H 10.577555 5.4 Z M 21.5,15 19.690025,13 H 24 28.309975 L 26.5,15 c -0.995486,1.1 -2.120486,2 -2.5,2 -0.379514,0 -1.504514,-0.9 -2.5,-2 z M 3,7.5817875 c 0,-1.3300169 0.273145,-3.1300169 0.6069888,-4 L 4.2139776,2 h 5.8845374 5.884537 l 1.249021,2 1.249021,2 H 23.740547 29 v 2 2 H 16 3 Z"
id="path132" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -25,6 +25,9 @@ import {
} from "../../api/js/egw_action/egw_keymanager"; } from "../../api/js/egw_action/egw_keymanager";
import {Et2UrlEmailReadonly} from "../../api/js/etemplate/Et2Url/Et2UrlEmailReadonly"; import {Et2UrlEmailReadonly} from "../../api/js/etemplate/Et2Url/Et2UrlEmailReadonly";
import {Et2SelectEmail} from "../../api/js/etemplate/Et2Select/Select/Et2SelectEmail"; import {Et2SelectEmail} from "../../api/js/etemplate/Et2Select/Select/Et2SelectEmail";
import {Et2SelectEmail} from "../../api/js/etemplate/Et2Select/Et2SelectEmail";
import {Et2Tree} from "../../api/js/etemplate/Et2TreeWidget/Et2Tree";
import {initMailTree} from "../../api/js/etemplate/Et2TreeWidget/MailTree";
/* required dependency, commented out because no module, but egw:uses is no longer parsed /* required dependency, commented out because no module, but egw:uses is no longer parsed
*/ */
@ -253,6 +256,9 @@ app.classes.mail = AppJS.extend(
var tree_wdg = this.et2.getWidgetById(this.nm_index+'[foldertree]'); var tree_wdg = this.et2.getWidgetById(this.nm_index+'[foldertree]');
if (tree_wdg) if (tree_wdg)
{ {
initMailTree();
tree_wdg.set_onopenstart(jQuery.proxy(this.openstart_tree, this)); tree_wdg.set_onopenstart(jQuery.proxy(this.openstart_tree, this));
tree_wdg.set_onopenend(jQuery.proxy(this.openend_tree, this)); tree_wdg.set_onopenend(jQuery.proxy(this.openend_tree, this));
} }

View File

@ -141,9 +141,17 @@
</et2-hbox> </et2-hbox>
</template> </template>
<template id="mail.index" template="" lang="" group="0" version="1.9.001"> <template id="mail.index" template="" lang="" group="0" version="1.9.001">
<!-- change back later TODO
<tree autoloading="mail.mail_ui.ajax_foldertree" id="nm[foldertree]" onclick="app.mail.mail_changeFolder" <tree autoloading="mail.mail_ui.ajax_foldertree" id="nm[foldertree]" onclick="app.mail.mail_changeFolder"
parent_node="mail-tree_target" onopenstart="app.mail.subscription_autoloadingStart" parent_node="mail-tree_target" onopenstart="app.mail.subscription_autoloadingStart"
onopenend="app.mail.subscription_autoloadingEnd" highlighting="true"/> onopenend="app.mail.subscription_autoloadingEnd" highlighting="true"/>
-->
<et2-tree autoLoading="mail.mail_ui.ajax_foldertree" id="nm[foldertree]" onClick="app.mail.mail_changeFolder"
parent_node="mail-tree_target" onOpenStart="app.mail.subscription_autoloadingStart"
onOpenEnd="app.mail.subscription_autoloadingEnd" highlighting="true"/>
<template id="splitter" height="100%" template="mail.index.splitter"/> <template id="splitter" height="100%" template="mail.index.splitter"/>
<iframe frameborder="1" id="extra_iframe" scrolling="auto" disabled="true"/> <iframe frameborder="1" id="extra_iframe" scrolling="auto" disabled="true"/>
</template> </template>