mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-28 19:03:14 +01:00
implement not strictly linked multiselect tree by using sl-tree selection="single" and sl-tree-item.selection:
- instead of showing checkboxes, we use the sl-tree-item.selection marker (blue left border) to show the multi-selection and sl-tree sl-selection-change event to set the value accordingly - implement Et2Tree.setSubChecked(_id, _value) to allow apps to (un)check a hierarchy onclick of parent, still allowing to (un)select single children - also change several tree methods to return the updateComplete promise to use in mail app.js instead of window.setInterval() to wait for tree loading
This commit is contained in:
parent
d1b3786b2a
commit
731a9d91af
@ -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()}
|
||||
<sl-tree
|
||||
part="tree"
|
||||
.selection=${this.multiple ? "multiple" : "single"}
|
||||
.selection=${/* implement unlinked multiple: this.multiple ? "multiple" :*/ "single"}
|
||||
@sl-selection-change=${
|
||||
(event: any) => {
|
||||
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);
|
||||
// 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")
|
||||
{
|
||||
// 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);
|
@ -63,6 +63,9 @@ export class Et2TreeDropdown extends SearchMixin<Constructor<any> & 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<Constructor<any> & Et2InputWidg
|
||||
._selectOptions=${this.select_options}
|
||||
.actions=${this.actions}
|
||||
.styleTemplate=${() => this.styleTemplate()}
|
||||
.autoloading="${this.autoloading}"
|
||||
|
||||
@sl-selection-change=${this.handleTreeChange}
|
||||
>
|
||||
|
@ -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)
|
||||
{
|
||||
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
|
||||
|
@ -6,8 +6,8 @@
|
||||
<et2-description value="Folder Management" class="mail_folder_management_header"></et2-description>
|
||||
</et2-hbox>
|
||||
<et2-hbox class="treeContainer">
|
||||
<et2-tree id="tree" multiple="true" autoloading="mail_ui::ajax_folderMgmtTree_autoloading" multimarking="strict"
|
||||
oncheck="app.mail.folderMgmt_onCheck" onselect="app.mail.folderMgmt_onSelect" highlighting="true"></et2-tree>
|
||||
<et2-tree id="tree" multiple="true" autoloading="mail_ui::ajax_folderMgmtTree_autoloading"
|
||||
x-onselect="app.mail.folderMgmt_onSelect" onclick="app.mail.foldertree_subselect"></et2-tree>
|
||||
</et2-hbox>
|
||||
<et2-hbox class="dialogFooterToolbar">
|
||||
<et2-button statustext="Delete" label="Delete" id="button[delete]" onclick="app.mail.folderMgmt_deleteBtn"></et2-button>
|
||||
|
@ -6,9 +6,7 @@
|
||||
<et2-description value="Subscription folders" class="mail_subscription_header"></et2-description>
|
||||
</et2-hbox>
|
||||
<et2-hbox class="treeContainer">
|
||||
<et2-tree id="foldertree" multiple="true" autoloading="mail_ui::ajax_tree_autoloading"
|
||||
multimarking="strict" highlighting="true" oncheck="app.mail.folderMgmt_onCheck"
|
||||
onselect="app.mail.folderMgmt_onSelect"></et2-tree>
|
||||
<et2-tree id="foldertree" multiple="true" onclick="app.mail.foldertree_subselect"></et2-tree>
|
||||
</et2-hbox>
|
||||
<et2-hbox class="dialogFooterToolbar">
|
||||
<et2-button statustext="Saves subscription changes" label="Save" id="button[save]"></et2-button>
|
||||
|
Loading…
Reference in New Issue
Block a user