diff --git a/api/js/etemplate/Et2Tree/Et2Tree.ts b/api/js/etemplate/Et2Tree/Et2Tree.ts index 9c72db349a..c54f912615 100644 --- a/api/js/etemplate/Et2Tree/Et2Tree.ts +++ b/api/js/etemplate/Et2Tree/Et2Tree.ts @@ -408,7 +408,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) return this._currentSlTreeItem } - getDomNode(_id): SlTreeItem + getDomNode(_id): SlTreeItem|null { return this.shadowRoot.querySelector("sl-tree-item[id='" + _id + "'"); } @@ -487,19 +487,20 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) * @param {string} _id ID of the node * @param {Object} [data] If provided, the item is refreshed directly with * the provided data instead of asking the server - * @return void + * @return Promise */ refreshItem(_id, data) { + /* TODO currently always ask the sever if (typeof data != "undefined" && data != null) { - //TODO currently always ask the sever + //data seems never to be used this.refreshItem(_id, null) - } else + } else*/ { let item = this.getNode(_id) - this.handleLazyLoading(item).then((result) => { + return this.handleLazyLoading(item).then((result) => { item.item = [...result.item] this.requestUpdate("_selectOptions") }) @@ -578,6 +579,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) * @param _id * @param _newItemId * @param _label + * @return Promise */ public renameItem(_id, _newItemId, _label) { @@ -602,6 +604,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) if (typeof _label != 'undefined') this.setLabel(_newItemId, _label); this.requestUpdate() + return this.updatedComplete(); } public focusItem(_id) @@ -610,7 +613,13 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) item.focused = true } - public openItem(_id) + /** + * Open an item, which might trigger lazy-loading + * + * @param string _id + * @return Promise + */ + public openItem(_id : string) { let item = this.getNode(_id); if(item) @@ -618,6 +627,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) item.open = 1; } this.requestUpdate(); + return this.updateComplete; } /** @@ -647,6 +657,38 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) } } + /** + * Set or unset checkbox of given node and all it's children based on given value + * + * @param _id + * @param _value "toggle" means the current nodes value, as the toggle already happened by default + * @return boolean false if _id was not found + */ + setSubChecked(_id : string, _value : boolean|"toggle") + { + const node = this.getDomNode(_id); + if (!node) return false; + + if (_value !== 'toggle') + { + node.selected = _value; + } + Array.from(node.querySelectorAll('sl-tree-item')).forEach((item : SlTreeItem) => { + item.selected = node.selected; + }); + // set selectedNodes and value + this.selectedNodes = []; + this.value = []; + Array.from(this._tree.querySelectorAll('sl-tree-item')).forEach((item : SlTreeItem) => { + if (item.selected) + { + this.selectedNodes.push(item); + this.value.push(item.id); + } + }); + return true; + } + getUserData(_nodeId, _name) { return this.getNode(_nodeId)?.userdata?.find(elem => elem.name === _name)?.content @@ -731,22 +773,57 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) ${this.styleTemplate()} { - this._previousOption = this._currentOption ?? (this.value.length ? this.getNode(this.value) : null); + this._previousOption = this._currentOption ?? (this.value.length ? this.getNode(this.value[0]) : null); this._currentOption = this.getNode(event.detail.selection[0].id) ?? this.optionSearch(event.detail.selection[0].id, this._selectOptions, 'id', 'item'); const ids = event.detail.selection.map(i => i.id); - this.value = this.multiple ? ids ?? [] : ids[0] ?? ""; + // implemented unlinked multiple + if (this.multiple) + { + const idx = this.value.indexOf(ids[0]); + if (idx < 0) + { + this.value.push(ids[0]); + } + else + { + this.value.splice(idx, 1); + } + // sync tree-items selected attribute with this.value + this.selectedNodes = []; + Array.from(this._tree.querySelectorAll('sl-tree-item')).forEach((item : SlTreeItem) => + { + if(this.value.includes(item.id)) + { + item.setAttribute("selected", ""); + this.selectedNodes.push(item); + } + else + { + item.removeAttribute("selected"); + } + }); + this._tree.requestUpdate(); + } + else + { + this.value = this.multiple ? ids ?? [] : ids[0] ?? ""; + } event.detail.previous = this._previousOption?.id; this._currentSlTreeItem = event.detail.selection[0]; + /* implemented unlinked-multiple if(this.multiple) { this.selectedNodes = event.detail.selection - } + }*/ if(typeof this.onclick == "function") { - this.onclick(event.detail.selection[0].id, this, event.detail.previous) + // wait for the update, so app founds DOM in the expected state + this._tree.updateComplete.then(() => { + this.onclick(event.detail.selection[0].id, this, event.detail.previous) + }); } } } @@ -939,22 +1016,6 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) } } } - - - - private createTree() - { - // 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 = ':}-*('; - // @ts-ignore from static get properties - - } - } customElements.define("et2-tree", Et2Tree); \ No newline at end of file diff --git a/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts b/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts index e324f84555..4d3cecd238 100644 --- a/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts +++ b/api/js/etemplate/Et2Tree/Et2TreeDropdown.ts @@ -63,6 +63,9 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg /** The component's help text. If you need to display HTML, use the `help-text` slot instead. */ @property({attribute: 'help-text'}) helpText = ""; + @property({type: String}) + autoloading: string = "" //description: "JSON URL or menuaction to be called for nodes marked with child=1, but not having children, getSelectedNode() contains node-id" + /** * Indicates whether the dropdown is open. You can toggle this attribute to show and hide the tree, or you can * use the `show()` and `hide()` methods and this attribute will reflect the open state. @@ -636,6 +639,7 @@ export class Et2TreeDropdown extends SearchMixin & Et2InputWidg ._selectOptions=${this.select_options} .actions=${this.actions} .styleTemplate=${() => this.styleTemplate()} + .autoloading="${this.autoloading}" @sl-selection-change=${this.handleTreeChange} > diff --git a/mail/js/app.js b/mail/js/app.js index e7bf69a276..9aad11157e 100755 --- a/mail/js/app.js +++ b/mail/js/app.js @@ -690,10 +690,8 @@ app.classes.mail = AppJS.extend( break; case 'add': const current_id = tree.getValue(); - tree.refreshItem(0); // refresh root - // ToDo: tree.refreshItem() and openItem() should return a promise // need to wait tree is refreshed: current and new id are there AND current folder is selected again - const interval = window.setInterval(() => { + tree.refreshItem(0).then(() => { if (tree.getNode(_id) && tree.getNode(current_id)) { if (!tree.getSelectedNode()) @@ -702,24 +700,21 @@ app.classes.mail = AppJS.extend( } else { - window.clearInterval(interval); // open new account - tree.openItem(_id, true); // need to wait new folders are loaded AND current folder is selected again - const open_interval = window.setInterval(() => { + tree.openItem(_id, true).then(() => { if (tree.getNode(_id + '::INBOX')) { if (!tree.getSelectedNode()) { tree.reSelectItem(current_id); } else { - window.clearInterval(open_interval); this.mail_changeFolder(_id + '::INBOX', tree, current_id); tree.reSelectItem(_id + '::INBOX'); } } - }, 200); + }); } } - }, 200); + }); break; default: // null } @@ -4548,24 +4543,6 @@ app.classes.mail = AppJS.extend( console.log(_data); }, - /** - * Submit on apply button and save current tree state - * - * @param {type} _egw - * @param {type} _widget - * @returns {undefined} - */ - subscription_apply: function (_egw, _widget) - { - var tree = etemplate2.getByApplication('mail')[0].widgetContainer.getWidgetById('foldertree'); - if (tree) - { - tree.input._xfullXML = true; - this.subscription_treeLastState = tree.input.serializeTreeToJSON(); - } - this.et2._inst.submit(_widget); - }, - /** * Popup the subscription dialog * @@ -4611,16 +4588,26 @@ app.classes.mail = AppJS.extend( }, /** - * Onclick for node/foldername in subscription popup + * Onclick for foldertree to (un)select children * * Used to (un)check node including all children * * @param {string} _id id of clicked node * @param {et2_tree} _widget reference to tree widget + * @param {PoinerEvent} _ev */ - subscribe_onclick: function(_id, _widget) + foldertree_subselect: function(_id, _widget, _ev) { - _widget.setSubChecked(_id, "toggle"); + const node = _widget.getNode(_id); + // do we need to autoload the subitems first + if (node.child && !node.item.length) + { + _widget.refreshItem(_id).then(() =>_widget.setSubChecked(_id, "toggle")); + } + else + { + _widget.setSubChecked(_id, "toggle"); + } }, /** @@ -5555,6 +5542,7 @@ app.classes.mail = AppJS.extend( }, /** + * Range selection for old dhtmlx tree currently NOT used * * @param {type} _ids * @param {type} _widget @@ -5572,7 +5560,7 @@ app.classes.mail = AppJS.extend( * * @param {string} _a start node id * @param {string} _b end node id - * @param {string} _branch totall node ids in the level + * @param {string} _branch total node ids in the level */ var rangeSelector = function(_a,_b, _branch) { @@ -5611,38 +5599,6 @@ app.classes.mail = AppJS.extend( } }, - /** - * Set enable/disable checkbox - * - * @param {object} _widget tree widget - * @param {string} _itemId item tree id - * @param {boolean} _stat - status to be set on checkbox true/false - */ - folderMgmt_setCheckbox: function (_widget, _itemId, _stat) - { - if (_widget) - { - _widget.input.setCheck(_itemId, _stat); - _widget.input.setSubChecked(_itemId,_stat); - } - }, - - /** - * - * @param {type} _id - * @param {type} _widget - * @TODO: Implement onCheck handler in order to select or deselect subItems - * of a checked parent node - */ - folderMgmt_onCheck: function (_id, _widget) - { - var selected = _widget.value; - if (selected && selected.split(_widget.input.dlmtr).length > 5) - { - egw.message(egw.lang('If you would like to select multiple folders in one action, you can hold ctrl key then select a folder as start range and another folder within a same level as end range, all folders in between will be selected or unselected based on their current status.'), 'success'); - } - }, - /** * Delete button handler * triggers longTask dialog and send delete operation url diff --git a/mail/templates/default/folder_management.xet b/mail/templates/default/folder_management.xet index 7aca895b67..cc965198b0 100644 --- a/mail/templates/default/folder_management.xet +++ b/mail/templates/default/folder_management.xet @@ -6,8 +6,8 @@ - + diff --git a/mail/templates/default/subscribe.xet b/mail/templates/default/subscribe.xet index b5e85861f2..25f84aae9e 100755 --- a/mail/templates/default/subscribe.xet +++ b/mail/templates/default/subscribe.xet @@ -6,9 +6,7 @@ - +