UNPKG

@progress/kendo-angular-treeview

Version:
142 lines (141 loc) 7.99 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { getter, setter } from '@progress/kendo-common'; import { take } from 'rxjs/operators'; import { DropPosition } from '../models'; import { expandDropTarget, collapseEmptyParent, updateMovedItemIndex } from '../drag-and-drop-utils'; import { incrementPageSize, copyPageSize, decrementPageSize } from '../../load-more/load-more-utils'; import { buildTreeIndex, getDataItem, isPresent } from '../../utils'; /** * @hidden */ export class FlatEditingService { flatBinding; movedItemNewIndex; constructor(flatBinding) { this.flatBinding = flatBinding; } add({ sourceItem, destinationItem, dropPosition, sourceTree, destinationTree }) { // shallow clone the item as not to mistake it for its 'older' version when the remove handler kicks in to splice the item at its old position const clonedSourceDataItem = Object.assign({}, getDataItem(sourceItem)); if (dropPosition === DropPosition.Over) { // expand the item that was dropped into expandDropTarget(destinationItem, destinationTree); const destinationItemId = getter(this.flatBinding.idField)(getDataItem(destinationItem)); setter(this.flatBinding.parentIdField)(clonedSourceDataItem, destinationItemId); const lastChildNodeIndex = this.getLastVisibleChildNodeIndex(destinationTree, this.flatBinding.originalData, getDataItem(destinationItem)); // insert after the last visible child const targetIndex = lastChildNodeIndex + 1; this.flatBinding.originalData.splice(targetIndex, 0, clonedSourceDataItem); // rebind the treeview data before searching for the focus target index this.rebindData(); const focusTarget = this.fetchChildNodes(getDataItem(destinationItem), destinationTree).indexOf(clonedSourceDataItem); this.movedItemNewIndex = buildTreeIndex(destinationItem.item.index, focusTarget); } else { const shiftIndex = dropPosition === DropPosition.After ? 1 : 0; const targetIndex = this.flatBinding.originalData.indexOf(getDataItem(destinationItem)) + shiftIndex; this.flatBinding.originalData.splice(targetIndex, 0, clonedSourceDataItem); const destinationItemParentId = getter(this.flatBinding.parentIdField)(getDataItem(destinationItem)); setter(this.flatBinding.parentIdField)(clonedSourceDataItem, destinationItemParentId); // rebind the treeview data before searching for the focus target index this.rebindData(); const parentIndex = destinationItem.parent ? destinationItem.parent.item.index : null; const parentContainer = destinationItem.parent ? this.fetchChildNodes(getDataItem(destinationItem.parent), destinationTree) : destinationTree.nodes; const focusTarget = parentContainer.indexOf(clonedSourceDataItem); this.movedItemNewIndex = buildTreeIndex(parentIndex, focusTarget); } if (sourceTree !== destinationTree) { this.addChildNodes(clonedSourceDataItem, sourceTree); } // increment the parent page size => an item is moved into it const updatedParent = dropPosition === DropPosition.Over ? getDataItem(destinationItem) : getDataItem(destinationItem.parent); incrementPageSize(destinationTree, updatedParent); // the page sizes are stored by data-item reference => copy the old item ref page size to the new item reference copyPageSize(destinationTree, getDataItem(sourceItem), clonedSourceDataItem); // the source tree nodes are reloaded on `removeItem` - reload the destination tree nodes if the soruce and the destination tree are different if (sourceTree !== destinationTree && !destinationTree.loadOnDemand) { destinationTree.preloadChildNodes(); } // if the source and destination trees are the same, focusing the moved item here will not have the desired effect // as the `remove` handler has not yet kicked-in to remove the item from its old position if (sourceTree !== destinationTree) { // ensure the focus target is rendered and registered destinationTree.changeDetectorRef.detectChanges(); destinationTree.focus(this.movedItemNewIndex); } } remove({ sourceItem, sourceTree, destinationTree }) { const sourceDataItem = getDataItem(sourceItem); const sourceItemIndex = this.flatBinding.originalData.indexOf(sourceDataItem); this.flatBinding.originalData.splice(sourceItemIndex, 1); if (sourceTree !== destinationTree) { this.removeChildNodes(sourceDataItem, sourceTree); } this.rebindData(); // emit collapse for the parent node if its last child node was spliced const parentChildren = sourceItem.parent ? sourceItem.parent.children : []; collapseEmptyParent(sourceItem.parent, parentChildren, sourceTree); // decrement source item parent page size => an item has been removed from it decrementPageSize(sourceTree, getDataItem(sourceItem.parent)); // reload the treeview nodes if (!sourceTree.loadOnDemand) { sourceTree.preloadChildNodes(); } // if the source and destination trees are different we want to focus only the moved item in the destination tree if (sourceTree === destinationTree) { // ensure the focus target is rendered and registered destinationTree.changeDetectorRef.detectChanges(); // after the source item is removed from its original position, the candidate index might have to be corrected const index = updateMovedItemIndex(this.movedItemNewIndex, sourceItem.item.index); destinationTree.focus(index); } } addChildNodes(dataItem, source) { const itemChildren = this.fetchAllDescendantNodes(dataItem, source); this.flatBinding.originalData.push(...itemChildren); } removeChildNodes(dataItem, source) { const sourceChildren = this.fetchAllDescendantNodes(dataItem, source); sourceChildren.forEach(item => { const index = this.flatBinding.originalData.indexOf(item); this.flatBinding.originalData.splice(index, 1); }); } fetchAllDescendantNodes(node, treeview) { let nodes = this.fetchChildNodes(node, treeview); nodes.forEach(node => nodes = nodes.concat(this.fetchAllDescendantNodes(node, treeview) || [])); return nodes; } fetchChildNodes(node, treeview) { if (!node) { return []; } let nodes = []; treeview .children(node) .pipe(take(1)) .subscribe(children => nodes = nodes.concat(children || [])); return nodes; } getLastVisibleChildNodeIndex(treeview, data, node) { if (!isPresent(treeview.loadMoreService) || !treeview.hasChildren(node)) { return data.length; } const visibleNodesCount = treeview.loadMoreService.getGroupSize(node); const visibleChildren = this.fetchChildNodes(node, treeview).slice(0, visibleNodesCount); const lastNode = visibleChildren[visibleChildren.length - 1]; const lastNodeIndex = data.indexOf(lastNode); return lastNodeIndex; } rebindData() { this.flatBinding.nodes = this.flatBinding.originalData; this.flatBinding.updateNodes(this.flatBinding.originalData); } }