UNPKG

@eclipse-scout/core

Version:
325 lines (283 loc) 11.2 kB
/* * Copyright (c) 2010, 2025 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import { App, arrays, CellModel, ChildModelOf, defaultValues, Event, ModelAdapter, objects, RemoteEvent, scout, Tree, TreeDropEvent, TreeNode, TreeNodeActionEvent, TreeNodeClickEvent, TreeNodeExpandedEvent, TreeNodeModel, TreeNodesCheckedEvent, TreeNodesSelectedEvent } from '../index'; export class TreeAdapter extends ModelAdapter { declare widget: Tree; constructor() { super(); this._addRemoteProperties(['displayStyle']); } override _postCreateWidget() { super._postCreateWidget(); this._autoCheckChildNodes(); } protected _autoCheckChildNodes() { if (!this.widget.autoCheckChildren) { return; } // When the Java model provides a checked parent node having non-checked child nodes // then we want the child nodes to be auto checked. Tree.visitNodes((node: TreeNode, parentNode: TreeNode) => { if (node.checked) { this.widget.checkNodes(node, {checked: true}); return true; // Skip subtree } }, this.widget.nodes, null); } protected _sendNodesSelected(nodeIds: string[], debounceSend: boolean) { let eventData = { nodeIds: nodeIds }; // send delayed to avoid a lot of requests while selecting // coalesce: only send the latest selection changed event for a field this._send('nodesSelected', eventData, { delay: (debounceSend ? 250 : 0), coalesce: function(previous: RemoteEvent) { return this.target === previous.target && this.type === previous.type; } }); } protected _onWidgetNodeClick(event: TreeNodeClickEvent) { this._send('nodeClick', { nodeId: event.node.id }); } protected _onWidgetNodeAction(event: TreeNodeActionEvent) { this._send('nodeAction', { nodeId: event.node.id }); } protected _onWidgetNodesSelected(event: TreeNodesSelectedEvent) { let nodeIds = this.widget.nodesToIds(this.widget.selectedNodes); this._sendNodesSelected(nodeIds, event.debounce); } protected _onWidgetNodeExpanded(event: TreeNodeExpandedEvent) { this._send('nodeExpanded', { nodeId: event.node.id, expanded: event.expanded, expandedLazy: event.expandedLazy }); } protected _onWidgetNodesChecked(event: TreeNodesCheckedEvent) { this._sendNodesChecked(event.nodes); } protected _sendNodesChecked(nodes: TreeNode[]) { let data = { nodes: [] }; for (let i = 0; i < nodes.length; i++) { data.nodes.push({ nodeId: nodes[i].id, checked: nodes[i].checked }); } this._send('nodesChecked', data); } protected override _onWidgetEvent(event: Event<Tree>) { if (event.type === 'nodesSelected') { this._onWidgetNodesSelected(event as TreeNodesSelectedEvent); } else if (event.type === 'nodeClick') { this._onWidgetNodeClick(event as TreeNodeClickEvent); } else if (event.type === 'nodeAction') { this._onWidgetNodeAction(event as TreeNodeActionEvent); } else if (event.type === 'nodeExpanded') { this._onWidgetNodeExpanded(event as TreeNodeExpandedEvent); } else if (event.type === 'nodesChecked') { this._onWidgetNodesChecked(event as TreeNodesCheckedEvent); } else if (event.type === 'drop' && this.widget.dragAndDropHandler) { this.widget.dragAndDropHandler.uploadFiles(event as TreeDropEvent); } else { super._onWidgetEvent(event); } } override onModelAction(event: RemoteEvent) { if (event.type === 'nodesInserted') { this._onNodesInserted(event.nodes, event.commonParentNodeId); } else if (event.type === 'nodesUpdated') { this._onNodesUpdated(event.nodes); } else if (event.type === 'nodesDeleted') { this._onNodesDeleted(event.nodeIds, event.commonParentNodeId); } else if (event.type === 'allChildNodesDeleted') { this._onAllChildNodesDeleted(event.commonParentNodeId); } else if (event.type === 'nodesSelected') { this._onNodesSelected(event.nodeIds); } else if (event.type === 'nodeExpanded') { this._onNodeExpanded(event.nodeId, event as any); } else if (event.type === 'nodeChanged') { this._onNodeChanged(event.nodeId, event as any); } else if (event.type === 'nodesChecked') { this._onNodesChecked(event.nodes); } else if (event.type === 'childNodeOrderChanged') { this._onChildNodeOrderChanged(event.childNodeIds, event.parentNodeId); } else if (event.type === 'requestFocus') { this._onRequestFocus(); } else if (event.type === 'scrollToSelection') { this._onScrollToSelection(); } else { super.onModelAction(event); } } protected _onNodesInserted(nodes: TreeNode[] | TreeNode, parentNodeId: string) { let parentNode: TreeNode; if (parentNodeId !== null && parentNodeId !== undefined) { parentNode = this.widget.nodesMap[parentNodeId]; if (!parentNode) { throw new Error('Parent node could not be found. Id: ' + parentNodeId); } } this.widget.insertNodes(nodes, parentNode); } protected _onNodesUpdated(nodes: TreeNode | TreeNode[]) { this.widget.updateNodes(nodes); } protected _onNodesDeleted(nodeIds: string[], parentNodeId: string) { // noinspection DuplicatedCode let parentNode: TreeNode; if (parentNodeId !== null && parentNodeId !== undefined) { parentNode = this.widget.nodesMap[parentNodeId]; if (!parentNode) { throw new Error('Parent node could not be found. Id: ' + parentNodeId); } } this.addFilterForWidgetEventType('nodesSelected'); this.addFilterForWidgetEventType('nodesChecked'); let nodes = this.widget.nodesByIds(nodeIds); this.widget.deleteNodes(nodes, parentNode); } protected _onAllChildNodesDeleted(parentNodeId: string) { // noinspection DuplicatedCode let parentNode: TreeNode; if (parentNodeId !== null && parentNodeId !== undefined) { parentNode = this.widget.nodesMap[parentNodeId]; if (!parentNode) { throw new Error('Parent node could not be found. Id: ' + parentNodeId); } } this.addFilterForWidgetEventType('nodesSelected'); this.addFilterForWidgetEventType('nodesChecked'); this.widget.deleteAllChildNodes(parentNode); } protected _onNodesSelected(nodeIds: string[]) { this.addFilterForWidgetEvent(widgetEvent => widgetEvent.type === 'nodesSelected' && arrays.equals(nodeIds, this.widget.nodesToIds(this.widget.selectedNodes))); let nodes = this.widget.nodesByIds(nodeIds); this.widget.selectNodes(nodes); } /** * @param event.expanded true, to expand the node * @param event.expandedLazy true, to expand the nodes lazily * @param event.recursive true, to expand the descendant nodes as well */ protected _onNodeExpanded(nodeId: string, event: RemoteEvent & { expanded: boolean; expandedLazy: boolean; recursive?: boolean }) { let node = this.widget.nodesMap[nodeId], options = { lazy: event.expandedLazy }; let affectedNodesMap = objects.createMap() as Record<string, boolean>; affectedNodesMap[nodeId] = true; if (event.recursive) { Tree.visitNodes(n => { affectedNodesMap[n.id] = true; }, node.childNodes); } this.addFilterForWidgetEvent((widgetEvent: TreeNodeExpandedEvent) => { return widgetEvent.type === 'nodeExpanded' && affectedNodesMap[widgetEvent.node.id] && event.expanded === widgetEvent.expanded && event.expandedLazy === widgetEvent.expandedLazy; }); this.widget.setNodeExpanded(node, event.expanded, options); if (event.recursive) { this.widget.setNodeExpandedRecursive(node.childNodes, event.expanded, options); } } protected _onNodeChanged(nodeId: string, cell: RemoteEvent & CellModel) { let node = this.widget.nodesMap[nodeId]; defaultValues.applyTo(cell, 'TreeNode'); node.text = cell.text; node.cssClass = cell.cssClass; node.iconId = cell.iconId; node.tooltipText = cell.tooltipText; node.foregroundColor = cell.foregroundColor; node.backgroundColor = cell.backgroundColor; node.font = cell.font; node.htmlEnabled = cell.htmlEnabled; this.widget.changeNode(node); } protected _onNodesChecked(nodes: { id: string; checked: boolean }[]) { let checkedNodes: TreeNode[] = [], uncheckedNodes: TreeNode[] = []; nodes.forEach(nodeData => { let node = this.widget.nodeById(nodeData.id); if (nodeData.checked) { checkedNodes.push(node); } else { uncheckedNodes.push(node); } }); this.widget.checkNodes(checkedNodes, { checked: true, checkOnlyEnabled: false }); this.widget.uncheckNodes(uncheckedNodes, { checkOnlyEnabled: false }); } protected _onChildNodeOrderChanged(childNodeIds: string[], parentNodeId: string) { let parentNode = this.widget.nodeById(parentNodeId); let nodes = this.widget.nodesByIds(childNodeIds); this.widget.updateNodeOrder(nodes, parentNode); } protected _onRequestFocus() { this.widget.focus(); } protected _onScrollToSelection() { this.widget.revealSelection(); } protected _initNodeModel(nodeModel?: TreeNodeModel): ChildModelOf<TreeNode> { nodeModel = nodeModel || {}; nodeModel.objectType = scout.nvl(nodeModel.objectType, this._getDefaultNodeObjectType()); defaultValues.applyTo(nodeModel); return nodeModel as ChildModelOf<TreeNode>; } protected _getDefaultNodeObjectType(): string { return 'TreeNode'; } protected static _updateMarkChildrenCheckedRemote(this: Tree & { modelAdapter: TreeAdapter; _updateMarkChildrenCheckedOrig }, node: TreeNode) { // In autoCheckChildren mode, don't change the checked state of parent nodes while the tree is initializing if nodes come from Java. // This is necessary to set the checked state of the child nodes correctly, see _autoCheckChildNodes. if (this.modelAdapter && this.autoCheckChildren && !this.initialized) { return; } return this._updateMarkChildrenCheckedOrig(node); } /** * 'this' in this function refers to the Tree */ protected static _createTreeNodeRemote(this: Tree & { modelAdapter: TreeAdapter; _createTreeNodeOrig }, nodeModel: TreeNodeModel) { if (this.modelAdapter) { nodeModel = this.modelAdapter._initNodeModel(nodeModel); } return this._createTreeNodeOrig(nodeModel); } /** * Static method to modify the prototype of Tree. */ static modifyTreePrototype() { if (!App.get().remote) { return; } objects.replacePrototypeFunction(Tree, '_createTreeNode', TreeAdapter._createTreeNodeRemote, true); objects.replacePrototypeFunction(Tree, '_updateMarkChildrenChecked', TreeAdapter._updateMarkChildrenCheckedRemote, true); } } App.addListener('bootstrap', TreeAdapter.modifyTreePrototype);