import { NgModule, Component, Input, AfterContentInit, OnDestroy, Output, EventEmitter, OnInit, EmbeddedViewRef, ViewContainerRef, ContentChildren, QueryList, TemplateRef, Inject, ElementRef, forwardRef, Host } from '@angular/core'; import { Optional } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TreeNode } from '../common/treenode'; import { PrimeNGSharedModule } from '../common/shared'; import { PrimeTemplate } from '../common/shared'; import { TreeDragDropService } from '../common/treedragdropservice'; import { Subscription } from 'rxjs/Subscription'; import { BlockableUI } from '../common/blockableui'; import { NzSelectModule, NzGridModule } from 'ng-zorro-antd'; @Component({ selector: 'p-treeNode', template: `
  • {{node.label}}
  • {{node.label}}
    ` }) export class UITreeNode implements OnInit { static ICON_CLASS: string = 'ui-treenode-icon anticon anticon-fw'; @Input() node: TreeNode; @Input() parentNode: TreeNode; @Input() root: boolean; @Input() index: number; @Input() firstChild: boolean; @Input() lastChild: boolean; constructor(@Inject(forwardRef(() => Tree)) public tree: Tree) { } draghoverPrev: boolean; draghoverNext: boolean; draghoverNode: boolean ngOnInit() { this.node.parent = this.parentNode; } getIcon() { let icon: string; if (this.node.icon) icon = this.node.icon; else icon = this.node.expanded && this.node.children && this.node.children.length ? this.node.expandedIcon : this.node.collapsedIcon; return UITreeNode.ICON_CLASS + ' ' + icon; } isLeaf() { return this.node.leaf == false ? false : !(this.node.children && this.node.children.length); } toggle(event: Event) { if (this.node.expanded) this.tree.onNodeCollapse.emit({ originalEvent: event, node: this.node }); else this.tree.onNodeExpand.emit({ originalEvent: event, node: this.node }); this.node.expanded = !this.node.expanded } /**鼠标悬浮,展示编辑图标 */ onNodeMouseover($event, node, flag) { node._hover = flag; $event.preventDefault(); } onNodeClick(event: MouseEvent) { this.tree.onNodeClick(event, this.node); } onNodeTouchEnd() { this.tree.onNodeTouchEnd(); } onNodeRightClick(event: MouseEvent) { this.tree.onNodeRightClick(event, this.node); } isSelected() { return this.tree.isSelected(this.node); } onDropPoint(event: Event, position: number) { event.preventDefault(); let dragNode = this.tree.dragNode; let dragNodeIndex = this.tree.dragNodeIndex; let dragNodeScope = this.tree.dragNodeScope; let isValidDropPointIndex = this.tree.dragNodeTree === this.tree ? (position === 1 || dragNodeIndex !== this.index - 1) : true; if (this.tree.allowDrop(dragNode, this.node, dragNodeScope) && isValidDropPointIndex) { let newNodeList = this.node.parent ? this.node.parent.children : this.tree.value; this.tree.dragNodeSubNodes.splice(dragNodeIndex, 1); let dropIndex = this.index; if (position < 0) { dropIndex = (this.tree.dragNodeSubNodes === newNodeList) ? ((this.tree.dragNodeIndex > this.index) ? this.index : this.index - 1) : this.index; newNodeList.splice(dropIndex, 0, dragNode); } else { dropIndex = newNodeList.length; newNodeList.push(dragNode); } this.tree.dragDropService.stopDrag({ node: dragNode, subNodes: this.node.parent ? this.node.parent.children : this.tree.value, index: dragNodeIndex }); this.tree.onNodeDrop.emit({ originalEvent: event, dragNode: dragNode, dropNode: this.node, dropIndex: dropIndex }); } this.draghoverPrev = false; this.draghoverNext = false; } onDropPointDragOver(event) { event.dataTransfer.dropEffect = 'move'; event.preventDefault(); } onDropPointDragEnter(event: Event, position: number) { if (this.tree.allowDrop(this.tree.dragNode, this.node, this.tree.dragNodeScope)) { if (position < 0) this.draghoverPrev = true; else this.draghoverNext = true; } } onDropPointDragLeave(event: Event) { this.draghoverPrev = false; this.draghoverNext = false; } onDragStart(event) { if (this.tree.draggableNodes && this.node.draggable !== false) { event.dataTransfer.setData("text", "data"); this.tree.dragDropService.startDrag({ tree: this, node: this.node, subNodes: this.node.parent ? this.node.parent.children : this.tree.value, index: this.index, scope: this.tree.draggableScope }); } else { event.preventDefault(); } } onDragStop(event) { this.tree.dragDropService.stopDrag({ node: this.node, subNodes: this.node.parent ? this.node.parent.children : this.tree.value, index: this.index }); } onDropNodeDragOver(event) { event.dataTransfer.dropEffect = 'move'; if (this.tree.droppableNodes) { event.preventDefault(); event.stopPropagation(); } } onDropNode(event) { if (this.tree.droppableNodes && this.node.droppable !== false) { event.preventDefault(); event.stopPropagation(); let dragNode = this.tree.dragNode; if (this.tree.allowDrop(dragNode, this.node, this.tree.dragNodeScope)) { let dragNodeIndex = this.tree.dragNodeIndex; this.tree.dragNodeSubNodes.splice(dragNodeIndex, 1); if (this.node.children) this.node.children.push(dragNode); else this.node.children = [dragNode]; this.tree.dragDropService.stopDrag({ node: dragNode, subNodes: this.node.parent ? this.node.parent.children : this.tree.value, index: this.tree.dragNodeIndex }); this.tree.onNodeDrop.emit({ originalEvent: event, dragNode: dragNode, dropNode: this.node, index: this.index }); } } this.draghoverNode = false; } onDropNodeDragEnter(event) { if (this.tree.droppableNodes && this.node.droppable !== false && this.tree.allowDrop(this.tree.dragNode, this.node, this.tree.dragNodeScope)) { this.draghoverNode = true; } } onDropNodeDragLeave(event) { if (this.tree.droppableNodes) { let rect = event.currentTarget.getBoundingClientRect(); if (event.x > rect.left + rect.width || event.x < rect.left || event.y >= Math.floor(rect.top + rect.height) || event.y < rect.top) { this.draghoverNode = false; } } } } @Component({ selector: 'p-tree', template: `
    {{emptyMessage}}
    {{emptyMessage}}
    ` }) export class Tree implements OnInit, AfterContentInit, OnDestroy, BlockableUI { @Input() value: TreeNode[]; @Input() selectionMode: string; @Input() selection: any; @Output() selectionChange: EventEmitter = new EventEmitter(); @Output() onNodeSelect: EventEmitter = new EventEmitter(); @Output() onNodeUnselect: EventEmitter = new EventEmitter(); @Output() onNodeExpand: EventEmitter = new EventEmitter(); @Output() onNodeCollapse: EventEmitter = new EventEmitter(); @Output() onNodeContextMenuSelect: EventEmitter = new EventEmitter(); @Output() onNodeDrop: EventEmitter = new EventEmitter(); @Input() style: any; // add by giscafer @Input() border: boolean = true; @Input() canCollapsed: boolean = true; @Input() styleClass: string; @Input() contextMenu: any; @Input() layout: string = 'vertical'; @Input() draggableScope: any; @Input() droppableScope: any; @Input() draggableNodes: boolean; @Input() droppableNodes: boolean; @Input() metaKeySelection: boolean = true; @Input() propagateSelectionUp: boolean = true; @Input() propagateSelectionDown: boolean = true; @Input() loading: boolean; @Input() loadingIcon: string = 'anticon-circle-o-notch'; @Input() emptyMessage: string = 'No records found'; @ContentChildren(PrimeTemplate) templates: QueryList; public templateMap: any; public nodeTouched: boolean; public dragNodeTree: Tree; public dragNode: TreeNode; public dragNodeSubNodes: TreeNode[]; public dragNodeIndex: number; public dragNodeScope: any; public dragHover: boolean; public dragStartSubscription: Subscription; public dragStopSubscription: Subscription; constructor(public el: ElementRef, @Optional() public dragDropService: TreeDragDropService) { } ngOnInit() { if (this.droppableNodes) { this.dragStartSubscription = this.dragDropService.dragStart$.subscribe( event => { this.dragNodeTree = event.tree; this.dragNode = event.node; this.dragNodeSubNodes = event.subNodes; this.dragNodeIndex = event.index; this.dragNodeScope = event.scope; }); this.dragStopSubscription = this.dragDropService.dragStop$.subscribe( event => { this.dragNodeTree = null; this.dragNode = null; this.dragNodeSubNodes = null; this.dragNodeIndex = null; this.dragNodeScope = null; this.dragHover = false; }); } } get horizontal(): boolean { return this.layout == 'horizontal'; } ngAfterContentInit() { if (this.templates.length) { this.templateMap = {}; } this.templates.forEach((item) => { this.templateMap[item.name] = item.template; }); } onNodeClick(event: MouseEvent, node: TreeNode) { let eventTarget = (event.target); if (eventTarget.className && eventTarget.className.indexOf('ui-tree-toggler') === 0) { return; } else if (this.selectionMode) { if (node.selectable === false) { return; } let index = this.findIndexInSelection(node); let selected = (index >= 0); if (this.isCheckboxSelectionMode()) { if (selected) { if (this.propagateSelectionDown) this.propagateDown(node, false); else this.selection = this.selection.filter((val, i) => i != index); if (this.propagateSelectionUp && node.parent) { this.propagateUp(node.parent, false); } this.selectionChange.emit(this.selection); this.onNodeUnselect.emit({ originalEvent: event, node: node }); } else { if (this.propagateSelectionDown) this.propagateDown(node, true); else this.selection = [...this.selection || [], node]; if (this.propagateSelectionUp && node.parent) { this.propagateUp(node.parent, true); } this.selectionChange.emit(this.selection); this.onNodeSelect.emit({ originalEvent: event, node: node }); } } else { let metaSelection = this.nodeTouched ? false : this.metaKeySelection; if (metaSelection) { let metaKey = (event.metaKey || event.ctrlKey); if (selected && metaKey) { if (this.isSingleSelectionMode()) { this.selectionChange.emit(null); } else { this.selection = this.selection.filter((val, i) => i != index); this.selectionChange.emit(this.selection); } this.onNodeUnselect.emit({ originalEvent: event, node: node }); } else { if (this.isSingleSelectionMode()) { this.selectionChange.emit(node); } else if (this.isMultipleSelectionMode()) { this.selection = (!metaKey) ? [] : this.selection || []; this.selection = [...this.selection, node]; this.selectionChange.emit(this.selection); } this.onNodeSelect.emit({ originalEvent: event, node: node }); } } else { if (this.isSingleSelectionMode()) { if (selected) { this.selection = null; this.onNodeUnselect.emit({ originalEvent: event, node: node }); } else { this.selection = node; this.onNodeSelect.emit({ originalEvent: event, node: node }); } } else { if (selected) { this.selection = this.selection.filter((val, i) => i != index); this.onNodeUnselect.emit({ originalEvent: event, node: node }); } else { this.selection = [...this.selection || [], node]; this.onNodeSelect.emit({ originalEvent: event, node: node }); } } this.selectionChange.emit(this.selection); } } } this.nodeTouched = false; } onNodeTouchEnd() { this.nodeTouched = true; } onNodeRightClick(event: MouseEvent, node: TreeNode) { if (this.contextMenu) { let eventTarget = (event.target); if (eventTarget.className && eventTarget.className.indexOf('ui-tree-toggler') === 0) { return; } else { let index = this.findIndexInSelection(node); let selected = (index >= 0); if (!selected) { if (this.isSingleSelectionMode()) this.selectionChange.emit(node); else this.selectionChange.emit([node]); } this.contextMenu.show(event); this.onNodeContextMenuSelect.emit({ originalEvent: event, node: node }); } } } findIndexInSelection(node: TreeNode) { let index: number = -1; if (this.selectionMode && this.selection) { if (this.isSingleSelectionMode()) { index = (this.selection == node) ? 0 : - 1; } else { for (let i = 0; i < this.selection.length; i++) { if (this.selection[i] == node) { index = i; break; } } } } return index; } propagateUp(node: TreeNode, select: boolean) { if (node.children && node.children.length) { let selectedCount: number = 0; let childPartialSelected: boolean = false; for (let child of node.children) { if (this.isSelected(child)) { selectedCount++; } else if (child.partialSelected) { childPartialSelected = true; } } if (select && selectedCount == node.children.length) { this.selection = [...this.selection || [], node]; node.partialSelected = false; } else { if (!select) { let index = this.findIndexInSelection(node); if (index >= 0) { this.selection = this.selection.filter((val, i) => i != index); } } if (childPartialSelected || selectedCount > 0 && selectedCount != node.children.length) node.partialSelected = true; else node.partialSelected = false; } } let parent = node.parent; if (parent) { this.propagateUp(parent, select); } } propagateDown(node: TreeNode, select: boolean) { let index = this.findIndexInSelection(node); if (select && index == -1) { this.selection = [...this.selection || [], node]; } else if (!select && index > -1) { this.selection = this.selection.filter((val, i) => i != index); } node.partialSelected = false; if (node.children && node.children.length) { for (let child of node.children) { this.propagateDown(child, select); } } } isSelected(node: TreeNode) { return this.findIndexInSelection(node) != -1; } isSingleSelectionMode() { return this.selectionMode && this.selectionMode == 'single'; } isMultipleSelectionMode() { return this.selectionMode && this.selectionMode == 'multiple'; } isCheckboxSelectionMode() { return this.selectionMode && this.selectionMode == 'checkbox'; } getTemplateForNode(node: TreeNode): TemplateRef { if (this.templateMap) return node.type ? this.templateMap[node.type] : this.templateMap['default']; else return null; } onDragOver(event) { if (this.droppableNodes && (!this.value || this.value.length === 0)) { event.dataTransfer.dropEffect = 'move'; event.preventDefault(); } } onDrop(event) { if (this.droppableNodes && (!this.value || this.value.length === 0)) { event.preventDefault(); let dragNode = this.dragNode; if (this.allowDrop(dragNode, null, this.dragNodeScope)) { let dragNodeIndex = this.dragNodeIndex; this.dragNodeSubNodes.splice(dragNodeIndex, 1); this.value = this.value || []; this.value.push(dragNode); this.dragDropService.stopDrag({ node: dragNode }); } } } onDragEnter(event) { if (this.droppableNodes && this.allowDrop(this.dragNode, null, this.dragNodeScope)) { this.dragHover = true; } } onDragLeave(event) { if (this.droppableNodes) { let rect = event.currentTarget.getBoundingClientRect(); if (event.x > rect.left + rect.width || event.x < rect.left || event.y > rect.top + rect.height || event.y < rect.top) { this.dragHover = false; } } } allowDrop(dragNode: TreeNode, dropNode: TreeNode, dragNodeScope: any): boolean { if (!dragNode) { //prevent random html elements to be dragged return false; } else if (this.isValidDragScope(dragNodeScope)) { let allow: boolean = true; if (dropNode) { if (dragNode === dropNode) { allow = false; } else { let parent = dropNode.parent; while (parent != null) { if (parent === dragNode) { allow = false; break; } parent = parent.parent; } } } return allow; } else { return false; } } isValidDragScope(dragScope: any): boolean { let dropScope = this.droppableScope; if (dropScope) { if (typeof dropScope === 'string') { if (typeof dragScope === 'string') return dropScope === dragScope; else if (dragScope instanceof Array) return (>dragScope).indexOf(dropScope) != -1; } else if (dropScope instanceof Array) { if (typeof dragScope === 'string') { return (>dropScope).indexOf(dragScope) != -1; } else if (dragScope instanceof Array) { for (let s of dropScope) { for (let ds of dragScope) { if (s === ds) { return true; } } } } } return false; } else { return true; } } getBlockableElement(): HTMLElement { return this.el.nativeElement.children[0]; } ngOnDestroy() { if (this.dragStartSubscription) { this.dragStartSubscription.unsubscribe(); } if (this.dragStopSubscription) { this.dragStopSubscription.unsubscribe(); } } } @NgModule({ imports: [CommonModule, NzSelectModule, NzGridModule], exports: [Tree, PrimeNGSharedModule], declarations: [Tree, UITreeNode] }) export class TreeModule { }