mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-27 09:09:04 +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
|
return this._currentSlTreeItem
|
||||||
}
|
}
|
||||||
|
|
||||||
getDomNode(_id): SlTreeItem
|
getDomNode(_id): SlTreeItem|null
|
||||||
{
|
{
|
||||||
return this.shadowRoot.querySelector("sl-tree-item[id='" + _id + "'");
|
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 {string} _id ID of the node
|
||||||
* @param {Object} [data] If provided, the item is refreshed directly with
|
* @param {Object} [data] If provided, the item is refreshed directly with
|
||||||
* the provided data instead of asking the server
|
* the provided data instead of asking the server
|
||||||
* @return void
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
refreshItem(_id, data)
|
refreshItem(_id, data)
|
||||||
{
|
{
|
||||||
|
/* TODO currently always ask the sever
|
||||||
if (typeof data != "undefined" && data != null)
|
if (typeof data != "undefined" && data != null)
|
||||||
{
|
{
|
||||||
//TODO currently always ask the sever
|
|
||||||
//data seems never to be used
|
//data seems never to be used
|
||||||
this.refreshItem(_id, null)
|
this.refreshItem(_id, null)
|
||||||
} else
|
} else*/
|
||||||
{
|
{
|
||||||
let item = this.getNode(_id)
|
let item = this.getNode(_id)
|
||||||
this.handleLazyLoading(item).then((result) => {
|
return this.handleLazyLoading(item).then((result) => {
|
||||||
item.item = [...result.item]
|
item.item = [...result.item]
|
||||||
this.requestUpdate("_selectOptions")
|
this.requestUpdate("_selectOptions")
|
||||||
})
|
})
|
||||||
@ -578,6 +579,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
|
|||||||
* @param _id
|
* @param _id
|
||||||
* @param _newItemId
|
* @param _newItemId
|
||||||
* @param _label
|
* @param _label
|
||||||
|
* @return Promise
|
||||||
*/
|
*/
|
||||||
public renameItem(_id, _newItemId, _label)
|
public renameItem(_id, _newItemId, _label)
|
||||||
{
|
{
|
||||||
@ -602,6 +604,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
|
|||||||
|
|
||||||
if (typeof _label != 'undefined') this.setLabel(_newItemId, _label);
|
if (typeof _label != 'undefined') this.setLabel(_newItemId, _label);
|
||||||
this.requestUpdate()
|
this.requestUpdate()
|
||||||
|
return this.updatedComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
public focusItem(_id)
|
public focusItem(_id)
|
||||||
@ -610,7 +613,13 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
|
|||||||
item.focused = true
|
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);
|
let item = this.getNode(_id);
|
||||||
if(item)
|
if(item)
|
||||||
@ -618,6 +627,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
|
|||||||
item.open = 1;
|
item.open = 1;
|
||||||
}
|
}
|
||||||
this.requestUpdate();
|
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)
|
getUserData(_nodeId, _name)
|
||||||
{
|
{
|
||||||
return this.getNode(_nodeId)?.userdata?.find(elem => elem.name === _name)?.content
|
return this.getNode(_nodeId)?.userdata?.find(elem => elem.name === _name)?.content
|
||||||
@ -731,22 +773,57 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement)
|
|||||||
${this.styleTemplate()}
|
${this.styleTemplate()}
|
||||||
<sl-tree
|
<sl-tree
|
||||||
part="tree"
|
part="tree"
|
||||||
.selection=${this.multiple ? "multiple" : "single"}
|
.selection=${/* implement unlinked multiple: this.multiple ? "multiple" :*/ "single"}
|
||||||
@sl-selection-change=${
|
@sl-selection-change=${
|
||||||
(event: any) => {
|
(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');
|
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);
|
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] ?? "";
|
this.value = this.multiple ? ids ?? [] : ids[0] ?? "";
|
||||||
|
}
|
||||||
event.detail.previous = this._previousOption?.id;
|
event.detail.previous = this._previousOption?.id;
|
||||||
this._currentSlTreeItem = event.detail.selection[0];
|
this._currentSlTreeItem = event.detail.selection[0];
|
||||||
|
/* implemented unlinked-multiple
|
||||||
if(this.multiple)
|
if(this.multiple)
|
||||||
{
|
{
|
||||||
this.selectedNodes = event.detail.selection
|
this.selectedNodes = event.detail.selection
|
||||||
}
|
}*/
|
||||||
if(typeof this.onclick == "function")
|
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)
|
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);
|
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. */
|
/** The component's help text. If you need to display HTML, use the `help-text` slot instead. */
|
||||||
@property({attribute: 'help-text'}) helpText = "";
|
@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
|
* 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.
|
* 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}
|
._selectOptions=${this.select_options}
|
||||||
.actions=${this.actions}
|
.actions=${this.actions}
|
||||||
.styleTemplate=${() => this.styleTemplate()}
|
.styleTemplate=${() => this.styleTemplate()}
|
||||||
|
.autoloading="${this.autoloading}"
|
||||||
|
|
||||||
@sl-selection-change=${this.handleTreeChange}
|
@sl-selection-change=${this.handleTreeChange}
|
||||||
>
|
>
|
||||||
|
@ -690,10 +690,8 @@ app.classes.mail = AppJS.extend(
|
|||||||
break;
|
break;
|
||||||
case 'add':
|
case 'add':
|
||||||
const current_id = tree.getValue();
|
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
|
// 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.getNode(_id) && tree.getNode(current_id))
|
||||||
{
|
{
|
||||||
if (!tree.getSelectedNode())
|
if (!tree.getSelectedNode())
|
||||||
@ -702,24 +700,21 @@ app.classes.mail = AppJS.extend(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
window.clearInterval(interval);
|
|
||||||
// open new account
|
// open new account
|
||||||
tree.openItem(_id, true);
|
|
||||||
// need to wait new folders are loaded AND current folder is selected again
|
// 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.getNode(_id + '::INBOX')) {
|
||||||
if (!tree.getSelectedNode()) {
|
if (!tree.getSelectedNode()) {
|
||||||
tree.reSelectItem(current_id);
|
tree.reSelectItem(current_id);
|
||||||
} else {
|
} else {
|
||||||
window.clearInterval(open_interval);
|
|
||||||
this.mail_changeFolder(_id + '::INBOX', tree, current_id);
|
this.mail_changeFolder(_id + '::INBOX', tree, current_id);
|
||||||
tree.reSelectItem(_id + '::INBOX');
|
tree.reSelectItem(_id + '::INBOX');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 200);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 200);
|
});
|
||||||
break;
|
break;
|
||||||
default: // null
|
default: // null
|
||||||
}
|
}
|
||||||
@ -4548,24 +4543,6 @@ app.classes.mail = AppJS.extend(
|
|||||||
console.log(_data);
|
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
|
* 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
|
* Used to (un)check node including all children
|
||||||
*
|
*
|
||||||
* @param {string} _id id of clicked node
|
* @param {string} _id id of clicked node
|
||||||
* @param {et2_tree} _widget reference to tree widget
|
* @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");
|
_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} _ids
|
||||||
* @param {type} _widget
|
* @param {type} _widget
|
||||||
@ -5572,7 +5560,7 @@ app.classes.mail = AppJS.extend(
|
|||||||
*
|
*
|
||||||
* @param {string} _a start node id
|
* @param {string} _a start node id
|
||||||
* @param {string} _b end 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)
|
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
|
* Delete button handler
|
||||||
* triggers longTask dialog and send delete operation url
|
* 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-description value="Folder Management" class="mail_folder_management_header"></et2-description>
|
||||||
</et2-hbox>
|
</et2-hbox>
|
||||||
<et2-hbox class="treeContainer">
|
<et2-hbox class="treeContainer">
|
||||||
<et2-tree id="tree" multiple="true" autoloading="mail_ui::ajax_folderMgmtTree_autoloading" multimarking="strict"
|
<et2-tree id="tree" multiple="true" autoloading="mail_ui::ajax_folderMgmtTree_autoloading"
|
||||||
oncheck="app.mail.folderMgmt_onCheck" onselect="app.mail.folderMgmt_onSelect" highlighting="true"></et2-tree>
|
x-onselect="app.mail.folderMgmt_onSelect" onclick="app.mail.foldertree_subselect"></et2-tree>
|
||||||
</et2-hbox>
|
</et2-hbox>
|
||||||
<et2-hbox class="dialogFooterToolbar">
|
<et2-hbox class="dialogFooterToolbar">
|
||||||
<et2-button statustext="Delete" label="Delete" id="button[delete]" onclick="app.mail.folderMgmt_deleteBtn"></et2-button>
|
<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-description value="Subscription folders" class="mail_subscription_header"></et2-description>
|
||||||
</et2-hbox>
|
</et2-hbox>
|
||||||
<et2-hbox class="treeContainer">
|
<et2-hbox class="treeContainer">
|
||||||
<et2-tree id="foldertree" multiple="true" autoloading="mail_ui::ajax_tree_autoloading"
|
<et2-tree id="foldertree" multiple="true" onclick="app.mail.foldertree_subselect"></et2-tree>
|
||||||
multimarking="strict" highlighting="true" oncheck="app.mail.folderMgmt_onCheck"
|
|
||||||
onselect="app.mail.folderMgmt_onSelect"></et2-tree>
|
|
||||||
</et2-hbox>
|
</et2-hbox>
|
||||||
<et2-hbox class="dialogFooterToolbar">
|
<et2-hbox class="dialogFooterToolbar">
|
||||||
<et2-button statustext="Saves subscription changes" label="Save" id="button[save]"></et2-button>
|
<et2-button statustext="Saves subscription changes" label="Save" id="button[save]"></et2-button>
|
||||||
|
Loading…
Reference in New Issue
Block a user