primeng
Version:
[](https://opensource.org/licenses/MIT) [](https://badge.fury.io/js/primeng) [ • 263 kB
JavaScript
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, ContentChildren, EventEmitter, forwardRef, Inject, Input, NgModule, Optional, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { PrimeTemplate, SharedModule, TranslationKeys } from 'primeng/api';
import { DomHandler } from 'primeng/dom';
import { RippleModule } from 'primeng/ripple';
import { ScrollerModule } from 'primeng/scroller';
import { ObjectUtils } from 'primeng/utils';
import * as i0 from "@angular/core";
import * as i1 from "@angular/common";
import * as i2 from "primeng/ripple";
import * as i3 from "primeng/api";
import * as i4 from "primeng/scroller";
export class UITreeNode {
constructor(tree) {
this.tree = tree;
}
ngOnInit() {
this.node.parent = this.parentNode;
if (this.parentNode) {
this.tree.syncNodeOption(this.node, this.tree.value, 'parent', this.tree.getNodeWithKey(this.parentNode.key, this.tree.value));
}
}
getIcon() {
let icon;
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.tree.isNodeLeaf(this.node);
}
toggle(event) {
if (this.node.expanded)
this.collapse(event);
else
this.expand(event);
event.stopPropagation();
}
expand(event) {
this.node.expanded = true;
if (this.tree.virtualScroll) {
this.tree.updateSerializedValue();
this.focusVirtualNode();
}
this.tree.onNodeExpand.emit({ originalEvent: event, node: this.node });
}
collapse(event) {
this.node.expanded = false;
if (this.tree.virtualScroll) {
this.tree.updateSerializedValue();
this.focusVirtualNode();
}
this.tree.onNodeCollapse.emit({ originalEvent: event, node: this.node });
}
onNodeClick(event) {
this.tree.onNodeClick(event, this.node);
}
onNodeKeydown(event) {
if (event.which === 13) {
this.tree.onNodeClick(event, this.node);
}
}
onNodeTouchEnd() {
this.tree.onNodeTouchEnd();
}
onNodeRightClick(event) {
this.tree.onNodeRightClick(event, this.node);
}
isSelected() {
return this.tree.isSelected(this.node);
}
onDropPoint(event, position) {
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 dropParams = { ...this.createDropPointEventMetadata(position) };
if (this.tree.validateDrop) {
this.tree.onNodeDrop.emit({
originalEvent: event,
dragNode: dragNode,
dropNode: this.node,
index: this.index,
accept: () => {
this.processPointDrop(dropParams);
}
});
}
else {
this.processPointDrop(dropParams);
this.tree.onNodeDrop.emit({
originalEvent: event,
dragNode: dragNode,
dropNode: this.node,
index: this.index
});
}
}
this.draghoverPrev = false;
this.draghoverNext = false;
}
processPointDrop(event) {
let newNodeList = event.dropNode.parent ? event.dropNode.parent.children : this.tree.value;
event.dragNodeSubNodes.splice(event.dragNodeIndex, 1);
let dropIndex = this.index;
if (event.position < 0) {
dropIndex = event.dragNodeSubNodes === newNodeList ? (event.dragNodeIndex > event.index ? event.index : event.index - 1) : event.index;
newNodeList.splice(dropIndex, 0, event.dragNode);
}
else {
dropIndex = newNodeList.length;
newNodeList.push(event.dragNode);
}
this.tree.dragDropService.stopDrag({
node: event.dragNode,
subNodes: event.dropNode.parent ? event.dropNode.parent.children : this.tree.value,
index: event.dragNodeIndex
});
}
createDropPointEventMetadata(position) {
return {
dragNode: this.tree.dragNode,
dragNodeIndex: this.tree.dragNodeIndex,
dragNodeSubNodes: this.tree.dragNodeSubNodes,
dropNode: this.node,
index: this.index,
position: position
};
}
onDropPointDragOver(event) {
event.dataTransfer.dropEffect = 'move';
event.preventDefault();
}
onDropPointDragEnter(event, position) {
if (this.tree.allowDrop(this.tree.dragNode, this.node, this.tree.dragNodeScope)) {
if (position < 0)
this.draghoverPrev = true;
else
this.draghoverNext = true;
}
}
onDropPointDragLeave(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) {
let dragNode = this.tree.dragNode;
if (this.tree.allowDrop(dragNode, this.node, this.tree.dragNodeScope)) {
let dropParams = { ...this.createDropNodeEventMetadata() };
if (this.tree.validateDrop) {
this.tree.onNodeDrop.emit({
originalEvent: event,
dragNode: dragNode,
dropNode: this.node,
index: this.index,
accept: () => {
this.processNodeDrop(dropParams);
}
});
}
else {
this.processNodeDrop(dropParams);
this.tree.onNodeDrop.emit({
originalEvent: event,
dragNode: dragNode,
dropNode: this.node,
index: this.index
});
}
}
}
event.preventDefault();
event.stopPropagation();
this.draghoverNode = false;
}
createDropNodeEventMetadata() {
return {
dragNode: this.tree.dragNode,
dragNodeIndex: this.tree.dragNodeIndex,
dragNodeSubNodes: this.tree.dragNodeSubNodes,
dropNode: this.node
};
}
processNodeDrop(event) {
let dragNodeIndex = event.dragNodeIndex;
event.dragNodeSubNodes.splice(dragNodeIndex, 1);
if (event.dropNode.children)
event.dropNode.children.push(event.dragNode);
else
event.dropNode.children = [event.dragNode];
this.tree.dragDropService.stopDrag({
node: event.dragNode,
subNodes: event.dropNode.parent ? event.dropNode.parent.children : this.tree.value,
index: dragNodeIndex
});
}
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;
}
}
}
onKeyDown(event) {
const nodeElement = event.target.parentElement.parentElement;
if (nodeElement.nodeName !== 'P-TREENODE' || (this.tree.contextMenu && this.tree.contextMenu.containerViewChild.nativeElement.style.display === 'block')) {
return;
}
switch (event.which) {
//down arrow
case 40:
const listElement = this.tree.droppableNodes ? nodeElement.children[1].children[1] : nodeElement.children[0].children[1];
if (listElement && listElement.children.length > 0) {
this.focusNode(listElement.children[0]);
}
else {
const nextNodeElement = nodeElement.nextElementSibling;
if (nextNodeElement) {
this.focusNode(nextNodeElement);
}
else {
let nextSiblingAncestor = this.findNextSiblingOfAncestor(nodeElement);
if (nextSiblingAncestor) {
this.focusNode(nextSiblingAncestor);
}
}
}
event.preventDefault();
break;
//up arrow
case 38:
if (nodeElement.previousElementSibling) {
this.focusNode(this.findLastVisibleDescendant(nodeElement.previousElementSibling));
}
else {
let parentNodeElement = this.getParentNodeElement(nodeElement);
if (parentNodeElement) {
this.focusNode(parentNodeElement);
}
}
event.preventDefault();
break;
//right arrow
case 39:
if (!this.node.expanded && !this.tree.isNodeLeaf(this.node)) {
this.expand(event);
}
event.preventDefault();
break;
//left arrow
case 37:
if (this.node.expanded) {
this.collapse(event);
}
else {
let parentNodeElement = this.getParentNodeElement(nodeElement);
if (parentNodeElement) {
this.focusNode(parentNodeElement);
}
}
event.preventDefault();
break;
//enter
case 13:
this.tree.onNodeClick(event, this.node);
event.preventDefault();
break;
default:
//no op
break;
}
}
findNextSiblingOfAncestor(nodeElement) {
let parentNodeElement = this.getParentNodeElement(nodeElement);
if (parentNodeElement) {
if (parentNodeElement.nextElementSibling)
return parentNodeElement.nextElementSibling;
else
return this.findNextSiblingOfAncestor(parentNodeElement);
}
else {
return null;
}
}
findLastVisibleDescendant(nodeElement) {
const listElement = Array.from(nodeElement.children).find((el) => DomHandler.hasClass(el, 'p-treenode'));
const childrenListElement = listElement.children[1];
if (childrenListElement && childrenListElement.children.length > 0) {
const lastChildElement = childrenListElement.children[childrenListElement.children.length - 1];
return this.findLastVisibleDescendant(lastChildElement);
}
else {
return nodeElement;
}
}
getParentNodeElement(nodeElement) {
const parentNodeElement = nodeElement.parentElement.parentElement.parentElement;
return parentNodeElement.tagName === 'P-TREENODE' ? parentNodeElement : null;
}
focusNode(element) {
if (this.tree.droppableNodes)
element.children[1].children[0].focus();
else
element.children[0].children[0].focus();
}
focusVirtualNode() {
this.timeout = setTimeout(() => {
let node = DomHandler.findSingle(document.body, `[data-id="${this.node.key ?? this.node.data}"]`);
DomHandler.focus(node);
}, 1);
}
}
UITreeNode.ICON_CLASS = 'p-treenode-icon ';
UITreeNode.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.0", ngImport: i0, type: UITreeNode, deps: [{ token: forwardRef(() => Tree) }], target: i0.ɵɵFactoryTarget.Component });
UITreeNode.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.1.0", type: UITreeNode, selector: "p-treeNode", inputs: { rowNode: "rowNode", node: "node", parentNode: "parentNode", root: "root", index: "index", firstChild: "firstChild", lastChild: "lastChild", level: "level", indentation: "indentation", itemSize: "itemSize" }, host: { classAttribute: "p-element" }, ngImport: i0, template: `
<ng-template [ngIf]="node">
<li
*ngIf="tree.droppableNodes"
class="p-treenode-droppoint"
[ngClass]="{ 'p-treenode-droppoint-active': draghoverPrev }"
(drop)="onDropPoint($event, -1)"
(dragover)="onDropPointDragOver($event)"
(dragenter)="onDropPointDragEnter($event, -1)"
(dragleave)="onDropPointDragLeave($event)"
></li>
<li *ngIf="!tree.horizontal" [ngClass]="['p-treenode', node.styleClass || '', isLeaf() ? 'p-treenode-leaf' : '']" [ngStyle]="{ height: itemSize + 'px' }" [style]="node.style">
<div
class="p-treenode-content"
[style.paddingLeft]="level * indentation + 'rem'"
(click)="onNodeClick($event)"
(contextmenu)="onNodeRightClick($event)"
(touchend)="onNodeTouchEnd()"
(drop)="onDropNode($event)"
(dragover)="onDropNodeDragOver($event)"
(dragenter)="onDropNodeDragEnter($event)"
(dragleave)="onDropNodeDragLeave($event)"
[draggable]="tree.draggableNodes"
(dragstart)="onDragStart($event)"
(dragend)="onDragStop($event)"
[attr.tabindex]="0"
[ngClass]="{ 'p-treenode-selectable': tree.selectionMode && node.selectable !== false, 'p-treenode-dragover': draghoverNode, 'p-highlight': isSelected() }"
role="treeitem"
(keydown)="onKeyDown($event)"
[attr.aria-posinset]="this.index + 1"
[attr.aria-expanded]="this.node.expanded"
[attr.aria-selected]="isSelected()"
[attr.aria-label]="node.label"
[attr.data-id]="node.key"
>
<button type="button" [attr.aria-label]="tree.togglerAriaLabel" class="p-tree-toggler p-link" (click)="toggle($event)" pRipple tabindex="-1">
<span class="p-tree-toggler-icon pi pi-fw" [ngClass]="{ 'pi-chevron-right': !node.expanded, 'pi-chevron-down': node.expanded }"></span>
</button>
<div class="p-checkbox p-component" [ngClass]="{ 'p-checkbox-disabled': node.selectable === false }" *ngIf="tree.selectionMode == 'checkbox'" [attr.aria-checked]="isSelected()">
<div class="p-checkbox-box" [ngClass]="{ 'p-highlight': isSelected(), 'p-indeterminate': node.partialSelected }">
<span class="p-checkbox-icon pi" [ngClass]="{ 'pi-check': isSelected(), 'pi-minus': node.partialSelected }"></span>
</div>
</div>
<span [class]="getIcon()" *ngIf="node.icon || node.expandedIcon || node.collapsedIcon"></span>
<span class="p-treenode-label">
<span *ngIf="!tree.getTemplateForNode(node)">{{ node.label }}</span>
<span *ngIf="tree.getTemplateForNode(node)">
<ng-container *ngTemplateOutlet="tree.getTemplateForNode(node); context: { $implicit: node }"></ng-container>
</span>
</span>
</div>
<ul class="p-treenode-children" style="display: none;" *ngIf="!tree.virtualScroll && node.children && node.expanded" [style.display]="node.expanded ? 'block' : 'none'" role="group">
<p-treeNode
*ngFor="let childNode of node.children; let firstChild = first; let lastChild = last; let index = index; trackBy: tree.trackBy"
[node]="childNode"
[parentNode]="node"
[firstChild]="firstChild"
[lastChild]="lastChild"
[index]="index"
[itemSize]="itemSize"
[level]="level + 1"
></p-treeNode>
</ul>
</li>
<li
*ngIf="tree.droppableNodes && lastChild"
class="p-treenode-droppoint"
[ngClass]="{ 'p-treenode-droppoint-active': draghoverNext }"
(drop)="onDropPoint($event, 1)"
(dragover)="onDropPointDragOver($event)"
(dragenter)="onDropPointDragEnter($event, 1)"
(dragleave)="onDropPointDragLeave($event)"
></li>
<table *ngIf="tree.horizontal" [class]="node.styleClass">
<tbody>
<tr>
<td class="p-treenode-connector" *ngIf="!root">
<table class="p-treenode-connector-table">
<tbody>
<tr>
<td [ngClass]="{ 'p-treenode-connector-line': !firstChild }"></td>
</tr>
<tr>
<td [ngClass]="{ 'p-treenode-connector-line': !lastChild }"></td>
</tr>
</tbody>
</table>
</td>
<td class="p-treenode" [ngClass]="{ 'p-treenode-collapsed': !node.expanded }">
<div
class="p-treenode-content"
tabindex="0"
[ngClass]="{ 'p-treenode-selectable': tree.selectionMode, 'p-highlight': isSelected() }"
(click)="onNodeClick($event)"
(contextmenu)="onNodeRightClick($event)"
(touchend)="onNodeTouchEnd()"
(keydown)="onNodeKeydown($event)"
>
<span [attr.aria-label]="tree.togglerAriaLabel" class="p-tree-toggler pi pi-fw" [ngClass]="{ 'pi-plus': !node.expanded, 'pi-minus': node.expanded }" *ngIf="!isLeaf()" (click)="toggle($event)"></span>
<span [class]="getIcon()" *ngIf="node.icon || node.expandedIcon || node.collapsedIcon"></span>
<span class="p-treenode-label">
<span *ngIf="!tree.getTemplateForNode(node)">{{ node.label }}</span>
<span *ngIf="tree.getTemplateForNode(node)">
<ng-container *ngTemplateOutlet="tree.getTemplateForNode(node); context: { $implicit: node }"></ng-container>
</span>
</span>
</div>
</td>
<td class="p-treenode-children-container" *ngIf="node.children && node.expanded" [style.display]="node.expanded ? 'table-cell' : 'none'">
<div class="p-treenode-children">
<p-treeNode *ngFor="let childNode of node.children; let firstChild = first; let lastChild = last; trackBy: tree.trackBy" [node]="childNode" [firstChild]="firstChild" [lastChild]="lastChild"></p-treeNode>
</div>
</td>
</tr>
</tbody>
</table>
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.Ripple, selector: "[pRipple]" }, { kind: "component", type: UITreeNode, selector: "p-treeNode", inputs: ["rowNode", "node", "parentNode", "root", "index", "firstChild", "lastChild", "level", "indentation", "itemSize"] }], encapsulation: i0.ViewEncapsulation.None });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.0", ngImport: i0, type: UITreeNode, decorators: [{
type: Component,
args: [{
selector: 'p-treeNode',
template: `
<ng-template [ngIf]="node">
<li
*ngIf="tree.droppableNodes"
class="p-treenode-droppoint"
[ngClass]="{ 'p-treenode-droppoint-active': draghoverPrev }"
(drop)="onDropPoint($event, -1)"
(dragover)="onDropPointDragOver($event)"
(dragenter)="onDropPointDragEnter($event, -1)"
(dragleave)="onDropPointDragLeave($event)"
></li>
<li *ngIf="!tree.horizontal" [ngClass]="['p-treenode', node.styleClass || '', isLeaf() ? 'p-treenode-leaf' : '']" [ngStyle]="{ height: itemSize + 'px' }" [style]="node.style">
<div
class="p-treenode-content"
[style.paddingLeft]="level * indentation + 'rem'"
(click)="onNodeClick($event)"
(contextmenu)="onNodeRightClick($event)"
(touchend)="onNodeTouchEnd()"
(drop)="onDropNode($event)"
(dragover)="onDropNodeDragOver($event)"
(dragenter)="onDropNodeDragEnter($event)"
(dragleave)="onDropNodeDragLeave($event)"
[draggable]="tree.draggableNodes"
(dragstart)="onDragStart($event)"
(dragend)="onDragStop($event)"
[attr.tabindex]="0"
[ngClass]="{ 'p-treenode-selectable': tree.selectionMode && node.selectable !== false, 'p-treenode-dragover': draghoverNode, 'p-highlight': isSelected() }"
role="treeitem"
(keydown)="onKeyDown($event)"
[attr.aria-posinset]="this.index + 1"
[attr.aria-expanded]="this.node.expanded"
[attr.aria-selected]="isSelected()"
[attr.aria-label]="node.label"
[attr.data-id]="node.key"
>
<button type="button" [attr.aria-label]="tree.togglerAriaLabel" class="p-tree-toggler p-link" (click)="toggle($event)" pRipple tabindex="-1">
<span class="p-tree-toggler-icon pi pi-fw" [ngClass]="{ 'pi-chevron-right': !node.expanded, 'pi-chevron-down': node.expanded }"></span>
</button>
<div class="p-checkbox p-component" [ngClass]="{ 'p-checkbox-disabled': node.selectable === false }" *ngIf="tree.selectionMode == 'checkbox'" [attr.aria-checked]="isSelected()">
<div class="p-checkbox-box" [ngClass]="{ 'p-highlight': isSelected(), 'p-indeterminate': node.partialSelected }">
<span class="p-checkbox-icon pi" [ngClass]="{ 'pi-check': isSelected(), 'pi-minus': node.partialSelected }"></span>
</div>
</div>
<span [class]="getIcon()" *ngIf="node.icon || node.expandedIcon || node.collapsedIcon"></span>
<span class="p-treenode-label">
<span *ngIf="!tree.getTemplateForNode(node)">{{ node.label }}</span>
<span *ngIf="tree.getTemplateForNode(node)">
<ng-container *ngTemplateOutlet="tree.getTemplateForNode(node); context: { $implicit: node }"></ng-container>
</span>
</span>
</div>
<ul class="p-treenode-children" style="display: none;" *ngIf="!tree.virtualScroll && node.children && node.expanded" [style.display]="node.expanded ? 'block' : 'none'" role="group">
<p-treeNode
*ngFor="let childNode of node.children; let firstChild = first; let lastChild = last; let index = index; trackBy: tree.trackBy"
[node]="childNode"
[parentNode]="node"
[firstChild]="firstChild"
[lastChild]="lastChild"
[index]="index"
[itemSize]="itemSize"
[level]="level + 1"
></p-treeNode>
</ul>
</li>
<li
*ngIf="tree.droppableNodes && lastChild"
class="p-treenode-droppoint"
[ngClass]="{ 'p-treenode-droppoint-active': draghoverNext }"
(drop)="onDropPoint($event, 1)"
(dragover)="onDropPointDragOver($event)"
(dragenter)="onDropPointDragEnter($event, 1)"
(dragleave)="onDropPointDragLeave($event)"
></li>
<table *ngIf="tree.horizontal" [class]="node.styleClass">
<tbody>
<tr>
<td class="p-treenode-connector" *ngIf="!root">
<table class="p-treenode-connector-table">
<tbody>
<tr>
<td [ngClass]="{ 'p-treenode-connector-line': !firstChild }"></td>
</tr>
<tr>
<td [ngClass]="{ 'p-treenode-connector-line': !lastChild }"></td>
</tr>
</tbody>
</table>
</td>
<td class="p-treenode" [ngClass]="{ 'p-treenode-collapsed': !node.expanded }">
<div
class="p-treenode-content"
tabindex="0"
[ngClass]="{ 'p-treenode-selectable': tree.selectionMode, 'p-highlight': isSelected() }"
(click)="onNodeClick($event)"
(contextmenu)="onNodeRightClick($event)"
(touchend)="onNodeTouchEnd()"
(keydown)="onNodeKeydown($event)"
>
<span [attr.aria-label]="tree.togglerAriaLabel" class="p-tree-toggler pi pi-fw" [ngClass]="{ 'pi-plus': !node.expanded, 'pi-minus': node.expanded }" *ngIf="!isLeaf()" (click)="toggle($event)"></span>
<span [class]="getIcon()" *ngIf="node.icon || node.expandedIcon || node.collapsedIcon"></span>
<span class="p-treenode-label">
<span *ngIf="!tree.getTemplateForNode(node)">{{ node.label }}</span>
<span *ngIf="tree.getTemplateForNode(node)">
<ng-container *ngTemplateOutlet="tree.getTemplateForNode(node); context: { $implicit: node }"></ng-container>
</span>
</span>
</div>
</td>
<td class="p-treenode-children-container" *ngIf="node.children && node.expanded" [style.display]="node.expanded ? 'table-cell' : 'none'">
<div class="p-treenode-children">
<p-treeNode *ngFor="let childNode of node.children; let firstChild = first; let lastChild = last; trackBy: tree.trackBy" [node]="childNode" [firstChild]="firstChild" [lastChild]="lastChild"></p-treeNode>
</div>
</td>
</tr>
</tbody>
</table>
</ng-template>
`,
encapsulation: ViewEncapsulation.None,
host: {
class: 'p-element'
}
}]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [forwardRef(() => Tree)]
}] }]; }, propDecorators: { rowNode: [{
type: Input
}], node: [{
type: Input
}], parentNode: [{
type: Input
}], root: [{
type: Input
}], index: [{
type: Input
}], firstChild: [{
type: Input
}], lastChild: [{
type: Input
}], level: [{
type: Input
}], indentation: [{
type: Input
}], itemSize: [{
type: Input
}] } });
export class Tree {
constructor(el, dragDropService, config, cd) {
this.el = el;
this.dragDropService = dragDropService;
this.config = config;
this.cd = cd;
this.layout = 'vertical';
this.metaKeySelection = true;
this.propagateSelectionUp = true;
this.propagateSelectionDown = true;
this.loadingIcon = 'pi pi-spinner';
this.emptyMessage = '';
this.filterBy = 'label';
this.filterMode = 'lenient';
this.lazy = false;
this.indentation = 1.5;
this.trackBy = (index, item) => item;
this.selectionChange = new EventEmitter();
this.onNodeSelect = new EventEmitter();
this.onNodeUnselect = new EventEmitter();
this.onNodeExpand = new EventEmitter();
this.onNodeCollapse = new EventEmitter();
this.onNodeContextMenuSelect = new EventEmitter();
this.onNodeDrop = new EventEmitter();
this.onLazyLoad = new EventEmitter();
this.onScroll = new EventEmitter();
this.onScrollIndexChange = new EventEmitter();
this.onFilter = new EventEmitter();
}
get virtualNodeHeight() {
return this._virtualNodeHeight;
}
set virtualNodeHeight(val) {
this._virtualNodeHeight = val;
console.warn('The virtualNodeHeight property is deprecated, use virtualScrollItemSize property instead.');
}
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;
});
}
}
ngOnChanges(simpleChange) {
if (simpleChange.value) {
this.updateSerializedValue();
}
}
get horizontal() {
return this.layout == 'horizontal';
}
get emptyMessageLabel() {
return this.emptyMessage || this.config.getTranslation(TranslationKeys.EMPTY_MESSAGE);
}
ngAfterContentInit() {
if (this.templates.length) {
this._templateMap = {};
}
this.templates.forEach((item) => {
switch (item.getType()) {
case 'header':
this.headerTemplate = item.template;
break;
case 'empty':
this.emptyMessageTemplate = item.template;
break;
case 'footer':
this.footerTemplate = item.template;
break;
case 'loader':
this.loaderTemplate = item.template;
break;
default:
this._templateMap[item.name] = item.template;
break;
}
});
}
updateSerializedValue() {
this.serializedValue = [];
this.serializeNodes(null, this.getRootNode(), 0, true);
}
serializeNodes(parent, nodes, level, visible) {
if (nodes && nodes.length) {
for (let node of nodes) {
node.parent = parent;
const rowNode = {
node: node,
parent: parent,
level: level,
visible: visible && (parent ? parent.expanded : true)
};
this.serializedValue.push(rowNode);
if (rowNode.visible && node.expanded) {
this.serializeNodes(node, node.children, level + 1, rowNode.visible);
}
}
}
}
onNodeClick(event, node) {
let eventTarget = event.target;
if (DomHandler.hasClass(eventTarget, 'p-tree-toggler') || DomHandler.hasClass(eventTarget, 'p-tree-toggler-icon')) {
return;
}
else if (this.selectionMode) {
if (node.selectable === false) {
return;
}
if (this.hasFilteredNodes()) {
node = this.getNodeWithKey(node.key, this.value);
if (!node) {
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, node) {
if (this.contextMenu) {
let eventTarget = event.target;
if (eventTarget.className && eventTarget.className.indexOf('p-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) {
let index = -1;
if (this.selectionMode && this.selection) {
if (this.isSingleSelectionMode()) {
let areNodesEqual = (this.selection.key && this.selection.key === node.key) || this.selection == node;
index = areNodesEqual ? 0 : -1;
}
else {
for (let i = 0; i < this.selection.length; i++) {
let selectedNode = this.selection[i];
let areNodesEqual = (selectedNode.key && selectedNode.key === node.key) || selectedNode == node;
if (areNodesEqual) {
index = i;
break;
}
}
}
}
return index;
}
syncNodeOption(node, parentNodes, option, value) {
// to synchronize the node option between the filtered nodes and the original nodes(this.value)
const _node = this.hasFilteredNodes() ? this.getNodeWithKey(node.key, parentNodes) : null;
if (_node) {
_node[option] = value || node[option];
}
}
hasFilteredNodes() {
return this.filter && this.filteredNodes && this.filteredNodes.length;
}
getNodeWithKey(key, nodes) {
for (let node of nodes) {
if (node.key === key) {
return node;
}
if (node.children) {
let matchedNode = this.getNodeWithKey(key, node.children);
if (matchedNode) {
return matchedNode;
}
}
}
}
propagateUp(node, select) {
if (node.children && node.children.length) {
let selectedCount = 0;
let childPartialSelected = 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;
}
this.syncNodeOption(node, this.filteredNodes, 'partialSelected');
}
let parent = node.parent;
if (parent) {
this.propagateUp(parent, select);
}
}
propagateDown(node, select) {
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;
this.syncNodeOption(node, this.filteredNodes, 'partialSelected');
if (node.children && node.children.length) {
for (let child of node.children) {
this.propagateDown(child, select);
}
}
}
isSelected(node) {
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';
}
isNodeLeaf(node) {
return node.leaf == false ? false : !(node.children && node.children.length);
}
getRootNode() {
return this.filteredNodes ? this.filteredNodes : this.value;
}
getTemplateForNode(node) {
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.value = this.value || [];
if (this.validateDrop) {
this.onNodeDrop.emit({
originalEvent: event,
dragNode: dragNode,
dropNode: null,
index: dragNodeIndex,
accept: () => {
this.processTreeDrop(dragNode, dragNodeIndex);
}
});
}
else {
this.onNodeDrop.emit({
originalEvent: event,
dragNode: dragNode,
dropNode: null,
index: dragNodeIndex
});
this.processTreeDrop(dragNode, dragNodeIndex);
}
}
}
}
processTreeDrop(dragNode, dragNodeIndex) {
this.dragNodeSubNodes.splice(dragNodeIndex, 1);
this.value.push(dragNode);
this.dragDropService.stopDrag({
node: dragNode
});
}
onDragEnter() {
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, dropNode, dragNodeScope) {
if (!dragNode) {
//prevent random html elements to be dragged
return false;
}
else if (this.isValidDragScope(dragNodeScope)) {
let allow = 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) {
let dropScope = this.droppableScope;
if (dropScope) {
if (typeof dropScope === 'string') {
if (typeof dragScope === 'string')
return dropScope === dragScope;
else if (Array.isArray(dragScope))
return dragScope.indexOf(dropScope) != -1;
}
else if (Array.isArray(dropScope)) {
if (typeof dragScope === 'string') {
return dropScope.indexOf(dragScope) != -1;
}
else if (Array.isArray(dragScope)) {
for (let s of dropScope) {
for (let ds of dragScope) {
if (s === ds) {
return true;
}
}
}
}
}
return false;
}
else {
return true;
}
}
_filter(value) {
let filterValue = value;
if (filterValue === '') {
this.filteredNodes = null;
}
else {
this.filteredNodes = [];
const searchFields = this.filterBy.split(',');
const filterText = ObjectUtils.removeAccents(filterValue).toLocaleLowerCase(this.filterLocale);
const isStrictMode = this.filterMode === 'strict';
for (let node of this.value) {
let copyNode = { ...node };
let paramsWithoutNode = { searchFields, filterText, isStrictMode };
if ((isStrictMode && (this.findFilteredNodes(copyNode, paramsWithoutNode) || this.isFilterMatched(copyNode, paramsWithoutNode))) ||
(!isStrictMode && (this.isFilterMatched(copyNode, paramsWithoutNode) || this.findFilteredNodes(copyNode, paramsWithoutNode)))) {
this.filteredNodes.push(copyNode);