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