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: `
`
})
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: `
`
})
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 { }