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 {SharedModule} from '../common/shared'; import {PrimeTemplate} from '../common/shared'; import {TreeDragDropService} from '../common/treedragdropservice'; import {Subscription} from 'rxjs/Subscription'; import {BlockableUI} from '../common/blockableui'; @Component({ selector: 'p-treeNode', template: `
  • {{node.label}}
  • {{node.label}}
    ` }) export class UITreeNode implements OnInit { static ICON_CLASS: string = 'ui-treenode-icon fa fa-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 } 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; @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 = 'fa-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], exports: [Tree,SharedModule], declarations: [Tree,UITreeNode] }) export class TreeModule { }