diff --git a/api/js/egw_action/EgwDragDropShoelaceTree.ts b/api/js/egw_action/EgwDragDropShoelaceTree.ts index f963783ec1..4a0c5fafa1 100644 --- a/api/js/egw_action/EgwDragDropShoelaceTree.ts +++ b/api/js/egw_action/EgwDragDropShoelaceTree.ts @@ -12,7 +12,7 @@ import {Et2Tree} from "../etemplate/Et2Tree/Et2Tree"; import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT, EGW_AO_STATE_FOCUSED, EGW_AO_STATE_SELECTED} from "./egw_action_constants"; import {egwBitIsSet} from "./egw_action_common"; import {SlTreeItem} from "@shoelace-style/shoelace"; -import {EgwActionObject} from "./EgwActionObject"; +import {FindActionTarget} from "../etemplate/FindActionTarget"; export const EXPAND_FOLDER_ON_DRAG_DROP_TIMEOUT = 1000 @@ -23,7 +23,7 @@ export class EgwDragDropShoelaceTree extends egwActionObjectInterface{ tree: Et2Tree; // Reference to the widget that's handling actions for us - public findActionTargetHandler : EgwActionObject; + public findActionTargetHandler : FindActionTarget; private timeout : ReturnType; @@ -32,12 +32,13 @@ export class EgwDragDropShoelaceTree extends egwActionObjectInterface{ super(); this.tree = _tree - this.findActionTargetHandler = _tree.widget_object; + this.findActionTargetHandler = _tree; } - public doTriggerEvent(egw_event : number, dom_event : Event) + public doTriggerEvent(egw_event : number, data : any) { - const target = this.tree.findActionTarget(dom_event); + let dom_event = data.event ?? data; + const target = this.findActionTargetHandler.findActionTarget(dom_event); if(egw_event == EGW_AI_DRAG_ENTER) { target.target.classList.add("draggedOver", "drop-hover"); @@ -54,10 +55,6 @@ export class EgwDragDropShoelaceTree extends egwActionObjectInterface{ target.target.classList.remove("draggedOver", "drop-hover"); clearTimeout(this.timeout) } - else - { - debugger; - } return true } @@ -71,16 +68,6 @@ export class EgwDragDropShoelaceTree extends egwActionObjectInterface{ { const target = this.tree.shadowRoot.querySelector("[id='" + this.stateChangeContext.id + "']"); - // Just set the attribute, we're not changing the tree value - // The selected attribute will be reset by the tree next render() - if(target && egwBitIsSet(_state, EGW_AO_STATE_SELECTED)) - { - target.setAttribute("selected", ""); - } - else if(target) - { - target.removeAttribute("selected"); - } if(target && egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)) { target.focus(); @@ -98,6 +85,11 @@ export class EgwDragDropShoelaceTree extends egwActionObjectInterface{ } } + getWidget() + { + return this.tree; + } + doGetDOMNode() { return this.tree; diff --git a/api/js/egw_action/EgwDropActionImplementation.ts b/api/js/egw_action/EgwDropActionImplementation.ts index 320da972b3..0f63c7f66e 100644 --- a/api/js/egw_action/EgwDropActionImplementation.ts +++ b/api/js/egw_action/EgwDropActionImplementation.ts @@ -46,7 +46,10 @@ export class EgwDropActionImplementation implements EgwActionImplementation { { _aoi.handlers = {}; } - _aoi.handlers[this.type] = []; + if(typeof _aoi.handlers[this.type] == "undefined") + { + _aoi.handlers[this.type] = []; + } node.classList.add('et2dropzone'); const dragover = (event)=> { if (event.preventDefault) { @@ -229,24 +232,7 @@ export class EgwDropActionImplementation implements EgwActionImplementation { event.preventDefault(); return false; }; - - // Bind events on parent, if provided, instead of individual node - if(_aoi.findActionTargetHandler) - { - // But only bind once - if(parentAO && !parentAO.iface.handlers[this.type]) - { - parentAO.iface.handlers[this.type] = parentAO.iface.handlers[this.type] ?? []; - // Swap objects, bind down below - _aoi = parentAO.iface; - node = parentAO.iface.getDOMNode(); - } - else - { - return true; - } - } - + if(_aoi.handlers[this.type].length == 0) { // DND Event listeners diff --git a/api/js/egw_action/EgwPopupActionImplementation.ts b/api/js/egw_action/EgwPopupActionImplementation.ts index d536fee22d..3f2fdecdab 100644 --- a/api/js/egw_action/EgwPopupActionImplementation.ts +++ b/api/js/egw_action/EgwPopupActionImplementation.ts @@ -41,20 +41,32 @@ export class EgwPopupActionImplementation implements EgwActionImplementation { _aoi.findActionTargetHandler = parentNode; isNew = true; } - - if(isNew) - { - //if a parent is available the context menu Event-listener will only be bound once on the parent - this._registerDefault(parentNode, _callback, parentAO); - this._registerContext(parentNode, _callback, parentAO); - - return true; - } - else if(node && !parentNode) + if(typeof _aoi.handlers == "undefined") { - this._registerDefault(node, _callback, _context); - this._registerContext(node, _callback, _context); - return true; + _aoi.handlers = {}; + } + if(typeof _aoi.handlers[this.type] == "undefined") + { + _aoi.handlers[this.type] = []; + } + + if(_aoi.handlers[this.type].length == 0) + { + _aoi.handlers[this.type].push({type: 'contextmenu', listener: _callback}); + if(isNew) + { + //if a parent is available the context menu Event-listener will only be bound once on the parent + this._registerDefault(parentNode, _callback, parentAO); + this._registerContext(parentNode, _callback, parentAO); + + return true; + } + else if(node && !parentNode) + { + this._registerDefault(node, _callback, _context); + this._registerContext(node, _callback, _context); + return true; + } } return false; @@ -64,6 +76,13 @@ export class EgwPopupActionImplementation implements EgwActionImplementation { const node = _aoi.getDOMNode(); //TODO jQuery replacement jQuery(node).off(); + + // Unregister handlers + if(_aoi.handlers) + { + _aoi.handlers[this.type]?.forEach(h => node.removeEventListener(h.type, h.listener)); + delete _aoi.handlers[this.type]; + } return true }; diff --git a/api/js/egw_action/egwDragActionImplementation.ts b/api/js/egw_action/egwDragActionImplementation.ts index ddcaa30b68..898395c099 100644 --- a/api/js/egw_action/egwDragActionImplementation.ts +++ b/api/js/egw_action/egwDragActionImplementation.ts @@ -133,7 +133,11 @@ export class EgwDragActionImplementation implements EgwActionImplementation { { _aoi.handlers = {}; } - _aoi.handlers[this.type] = []; + if(typeof _aoi.handlers[this.type] == "undefined") + { + _aoi.handlers[this.type] = []; + } + // Prevent selection node.onselectstart = function () { return false; @@ -150,7 +154,13 @@ export class EgwDragActionImplementation implements EgwActionImplementation { return; } - // Bind mouse handlers + if(_aoi.handlers[this.type].length !== 0) + { + // Already bound + return; + } + + // Bind mouse handlers //et2_dataview_view_aoi binds mousedown event in et2_dataview_rowAOI to "egwPreventSelect" function from egw_action_common via jQuery.mousedown //jQuery(node).off("mousedown",egwPreventSelect) //et2_dataview_view_aoi binds mousedown event in et2_dataview_rowAOI to "egwPreventSelect" function from egw_action_common via addEventListener @@ -291,12 +301,11 @@ export class EgwDragActionImplementation implements EgwActionImplementation { }; // Drag Event listeners - node.addEventListener('dragstart', dragstart, false); + node.addEventListener('dragstart', dragstart, false); _aoi.handlers[this.type].push({type: 'dragstart', listener: dragstart}); - node.addEventListener('dragend', dragend, false); + node.addEventListener('dragend', dragend, false); _aoi.handlers[this.type].push({type: 'dragend', listener: dragend}); - return true; } return false; diff --git a/api/js/etemplate/Et2Tree/Et2Tree.ts b/api/js/etemplate/Et2Tree/Et2Tree.ts index 95e72b7eda..d746c4d6c6 100644 --- a/api/js/etemplate/Et2Tree/Et2Tree.ts +++ b/api/js/etemplate/Et2Tree/Et2Tree.ts @@ -13,7 +13,7 @@ import {EgwActionObject} from "../../egw_action/EgwActionObject"; import {EgwAction} from "../../egw_action/EgwAction"; import {EgwDragDropShoelaceTree} from "../../egw_action/EgwDragDropShoelaceTree"; import {FindActionTarget} from "../FindActionTarget"; -import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT} from "../../egw_action/egw_action_constants"; +import {EGW_AI_DRAG_ENTER, EGW_AI_DRAG_OUT, EGW_AO_FLAG_IS_CONTAINER} from "../../egw_action/egw_action_constants"; export type TreeItemData = SelectOption & { focused?: boolean; @@ -849,9 +849,8 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) implements Fin const typeMap = { dragenter: EGW_AI_DRAG_ENTER, dragleave: EGW_AI_DRAG_OUT, - drop: EGW_AI_DRAG_OUT, } - this.widget_object.getObjectById(id).iface.triggerEvent(typeMap[event.type], event); + this.widget_object.iface.triggerEvent(typeMap[event.type] ?? event.type, event); } protected async finishedLazyLoading() @@ -946,7 +945,6 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) implements Fin this.getDomNode(parentNode.id).loading = false } this.requestUpdate("_selectOptions") - this._link_actions(this.actions) }) } @@ -1102,16 +1100,6 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) implements Fin itemAO.remove(); } - // Need the DOM nodes to actually link the actions - this.updateComplete.then(() => - { - this.linkLeafActions( - parentAO ?? this.widget_object, - _item, - this._get_action_links(this.actions) - ); - }); - return results; }); } @@ -1130,19 +1118,22 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) implements Fin // Get the top level element for the tree let objectManager = egw_getAppObjectManager(true); this.widget_object = objectManager.getObjectById(this.id); + const ao_impl = new et2_action_object_impl(this, this); + ao_impl.aoi = new EgwDragDropShoelaceTree(this); if (this.widget_object == null) { // Add a new container to the object manager which will hold the widget // objects this.widget_object = objectManager.insertObject(false, new EgwActionObject( //@ts-ignore - this.id, objectManager, (new et2_action_object_impl(this, this)).getAOI(), - this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager + this.id, objectManager, ao_impl.getAOI(), + this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager, + EGW_AO_FLAG_IS_CONTAINER )); } else { // @ts-ignore - this.widget_object.setAOI((new et2_action_object_impl(this, this)).getAOI()); + this.widget_object.setAOI(ao_impl.getAOI()); } // Delete all old objects @@ -1153,41 +1144,6 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) implements Fin // 'allowed' for this widget at this time var action_links = this._get_action_links(actions); this.widget_object.updateActionLinks(action_links); - //Drop target enabeling - if (typeof this._selectOptions != 'undefined') - { - let self: Et2Tree = this - // Iterate over the options (leaves) and add action to each one - for (const selectOption of this._selectOptions) - { - this.linkLeafActions(this.widget_object, selectOption, action_links) - } - } - } - - /** - * Add actions on a leaf - * - * @param {EgwActionObject} parentActionObject - * @param {TreeItemData} option - * @param {string[]} action_links - * @protected - */ - protected linkLeafActions(parentActionObject : EgwActionObject, option : TreeItemData, action_links : string[]) - { - // Add a new action object to the object manager - let id = option.value ?? (typeof option.id == 'number' ? String(option.id) : option.id); - - // @ts-ignore - let obj : EgwActionObject = parentActionObject.addObject(id, new EgwDragDropShoelaceTree(this, id)); - obj.findActionTargetHandler = this; - obj.updateActionLinks(action_links); - - const children = (option.children ?? option.item) ?? []; - for(let i = 0; i < children.length; i++) - { - this.linkLeafActions(obj, children[i], action_links); - } } /** @@ -1280,7 +1236,7 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) implements Fin } /** - * returns the closest SlItem to the click position, and the corresponding EgwActionObject + * returns the closest SlTreeItem to the click position, and the corresponding EgwActionObject * @param _event the click event * @returns { target:SlTreeItem, action:EgwActionObject } */ @@ -1292,11 +1248,18 @@ export class Et2Tree extends Et2WidgetWithSelectMixin(LitElement) implements Fin }); let action : EgwActionObject = this.widget_object.getObjectById(target.id); - // Create on the fly if not there? + // Create on the fly if not there? Action handlers might need the EgwActionObject if(!action) { - debugger; + // NOTE: FLAT object structure under the tree ActionObject to avoid nested selection + action = this.widget_object.addObject(target.id, this.widget_object.iface); + // Required to get dropped accepted, but also re-binds + action.updateActionLinks(this._get_action_links(this.actions)); } + // This is just the action system, which we override + this.widget_object.setAllSelected(false); + action.setSelected(true); + return {target: target, action: action}; } }