UNPKG

primeng

Version:

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![npm version](https://badge.fury.io/js/primeng.svg)](https://badge.fury.io/js/primeng) [![npm downloads](https://img.shields.io/npm/dm/primeng.sv

1 lines 210 kB
{"version":3,"file":"primeng-treetable.mjs","sources":["../../src/app/components/treetable/treetable.ts","../../src/app/components/treetable/primeng-treetable.ts"],"sourcesContent":["import { NgModule, AfterContentInit, OnInit, OnDestroy, HostListener, Injectable, Directive, Component, Input, Output, EventEmitter, ContentChildren, TemplateRef, QueryList, ElementRef, NgZone, ViewChild, AfterViewInit, AfterViewChecked, OnChanges, SimpleChanges, ChangeDetectionStrategy, ViewEncapsulation, ChangeDetectorRef} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { TreeNode } from 'primeng/api';\nimport { Subject, Subscription } from 'rxjs';\nimport { DomHandler } from 'primeng/dom';\nimport { PaginatorModule } from 'primeng/paginator';\nimport { PrimeTemplate, SharedModule, FilterService } from 'primeng/api';\nimport { SortMeta } from 'primeng/api';\nimport { BlockableUI } from 'primeng/api';\nimport { FilterMetadata } from 'primeng/api';\nimport { ObjectUtils } from 'primeng/utils';\nimport { RippleModule } from 'primeng/ripple';\nimport { Scroller, ScrollerModule, ScrollerOptions } from 'primeng/scroller';\n\n@Injectable()\nexport class TreeTableService {\n\n private sortSource = new Subject<SortMeta|SortMeta[]>();\n private selectionSource = new Subject();\n private contextMenuSource = new Subject<any>();\n private uiUpdateSource = new Subject<any>();\n private totalRecordsSource = new Subject<any>();\n\n sortSource$ = this.sortSource.asObservable();\n selectionSource$ = this.selectionSource.asObservable();\n contextMenuSource$ = this.contextMenuSource.asObservable();\n uiUpdateSource$ = this.uiUpdateSource.asObservable();\n totalRecordsSource$ = this.totalRecordsSource.asObservable();\n\n onSort(sortMeta: SortMeta|SortMeta[]) {\n this.sortSource.next(sortMeta);\n }\n\n onSelectionChange() {\n this.selectionSource.next(null);\n }\n\n onContextMenu(node: any) {\n this.contextMenuSource.next(node);\n }\n\n onUIUpdate(value: any) {\n this.uiUpdateSource.next(value);\n }\n\n onTotalRecordsChange(value: number) {\n this.totalRecordsSource.next(value);\n }\n}\n\n@Component({\n selector: 'p-treeTable',\n template: `\n <div #container [ngStyle]=\"style\" [class]=\"styleClass\" data-scrollselectors=\".p-treetable-scrollable-body\"\n [ngClass]=\"{'p-treetable p-component': true,\n 'p-treetable-hoverable-rows': (rowHover||(selectionMode === 'single' || selectionMode === 'multiple')),\n 'p-treetable-auto-layout': autoLayout,\n 'p-treetable-resizable': resizableColumns,\n 'p-treetable-resizable-fit': (resizableColumns && columnResizeMode === 'fit'),\n 'p-treetable-flex-scrollable': (scrollable && scrollHeight === 'flex')}\">\n <div class=\"p-treetable-loading\" *ngIf=\"loading && showLoader\">\n <div class=\"p-treetable-loading-overlay p-component-overlay\">\n <i [class]=\"'p-treetable-loading-icon pi-spin ' + loadingIcon\"></i>\n </div>\n </div>\n <div *ngIf=\"captionTemplate\" class=\"p-treetable-header\">\n <ng-container *ngTemplateOutlet=\"captionTemplate\"></ng-container>\n </div>\n <p-paginator [rows]=\"rows\" [first]=\"first\" [totalRecords]=\"totalRecords\" [pageLinkSize]=\"pageLinks\" styleClass=\"p-paginator-top\" [alwaysShow]=\"alwaysShowPaginator\"\n (onPageChange)=\"onPageChange($event)\" [rowsPerPageOptions]=\"rowsPerPageOptions\" *ngIf=\"paginator && (paginatorPosition === 'top' || paginatorPosition =='both')\"\n [templateLeft]=\"paginatorLeftTemplate\" [templateRight]=\"paginatorRightTemplate\" [dropdownAppendTo]=\"paginatorDropdownAppendTo\"\n [currentPageReportTemplate]=\"currentPageReportTemplate\" [showFirstLastIcon]=\"showFirstLastIcon\" [dropdownItemTemplate]=\"paginatorDropdownItemTemplate\" [showCurrentPageReport]=\"showCurrentPageReport\" [showJumpToPageDropdown]=\"showJumpToPageDropdown\" [showPageLinks]=\"showPageLinks\"></p-paginator>\n\n <div class=\"p-treetable-wrapper\" *ngIf=\"!scrollable\">\n <table #table [ngClass]=\"tableStyleClass\" [ngStyle]=\"tableStyle\">\n <ng-container *ngTemplateOutlet=\"colGroupTemplate; context {$implicit: columns}\"></ng-container>\n <thead class=\"p-treetable-thead\">\n <ng-container *ngTemplateOutlet=\"headerTemplate; context: {$implicit: columns}\"></ng-container>\n </thead>\n <tbody class=\"p-treetable-tbody\" [pTreeTableBody]=\"columns\" [pTreeTableBodyTemplate]=\"bodyTemplate\"></tbody>\n <tfoot class=\"p-treetable-tfoot\">\n <ng-container *ngTemplateOutlet=\"footerTemplate; context {$implicit: columns}\"></ng-container>\n </tfoot>\n </table>\n </div>\n\n <div class=\"p-treetable-scrollable-wrapper\" *ngIf=\"scrollable\">\n <div class=\"p-treetable-scrollable-view p-treetable-frozen-view\" *ngIf=\"frozenColumns||frozenBodyTemplate\" #scrollableFrozenView [ttScrollableView]=\"frozenColumns\" [frozen]=\"true\" [ngStyle]=\"{width: frozenWidth}\" [scrollHeight]=\"scrollHeight\"></div>\n <div class=\"p-treetable-scrollable-view\" #scrollableView [ttScrollableView]=\"columns\" [frozen]=\"false\" [scrollHeight]=\"scrollHeight\" [ngStyle]=\"{left: frozenWidth, width: 'calc(100% - '+frozenWidth+')'}\"></div>\n </div>\n\n <p-paginator [rows]=\"rows\" [first]=\"first\" [totalRecords]=\"totalRecords\" [pageLinkSize]=\"pageLinks\" styleClass=\"p-paginator-bottom\" [alwaysShow]=\"alwaysShowPaginator\"\n (onPageChange)=\"onPageChange($event)\" [rowsPerPageOptions]=\"rowsPerPageOptions\" *ngIf=\"paginator && (paginatorPosition === 'bottom' || paginatorPosition =='both')\"\n [templateLeft]=\"paginatorLeftTemplate\" [templateRight]=\"paginatorRightTemplate\" [dropdownAppendTo]=\"paginatorDropdownAppendTo\"\n [currentPageReportTemplate]=\"currentPageReportTemplate\" [showFirstLastIcon]=\"showFirstLastIcon\" [dropdownItemTemplate]=\"paginatorDropdownItemTemplate\" [showCurrentPageReport]=\"showCurrentPageReport\" [showJumpToPageDropdown]=\"showJumpToPageDropdown\" [showPageLinks]=\"showPageLinks\"></p-paginator>\n <div *ngIf=\"summaryTemplate\" class=\"p-treetable-footer\">\n <ng-container *ngTemplateOutlet=\"summaryTemplate\"></ng-container>\n </div>\n\n <div #resizeHelper class=\"p-column-resizer-helper\" style=\"display:none\" *ngIf=\"resizableColumns\"></div>\n\n <span #reorderIndicatorUp class=\"pi pi-arrow-down p-treetable-reorder-indicator-up\" *ngIf=\"reorderableColumns\"></span>\n <span #reorderIndicatorDown class=\"pi pi-arrow-up p-treetable-reorder-indicator-down\" *ngIf=\"reorderableColumns\"></span>\n </div>\n `,\n providers: [TreeTableService],\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['./treetable.css'],\n host: {\n 'class': 'p-element'\n }\n})\nexport class TreeTable implements AfterContentInit, OnInit, OnDestroy, BlockableUI, OnChanges {\n\n @Input() columns: any[];\n\n @Input() style: any;\n\n @Input() styleClass: string;\n\n @Input() tableStyle: any;\n\n @Input() tableStyleClass: string;\n\n @Input() autoLayout: boolean;\n\n @Input() lazy: boolean = false;\n\n @Input() lazyLoadOnInit: boolean = true;\n\n @Input() paginator: boolean;\n\n @Input() rows: number;\n\n @Input() first: number = 0;\n\n @Input() pageLinks: number = 5;\n\n @Input() rowsPerPageOptions: any[];\n\n @Input() alwaysShowPaginator: boolean = true;\n\n @Input() paginatorPosition: string = 'bottom';\n\n @Input() paginatorDropdownAppendTo: any;\n\n @Input() currentPageReportTemplate: string = '{currentPage} of {totalPages}';\n\n @Input() showCurrentPageReport: boolean;\n\n @Input() showJumpToPageDropdown: boolean;\n\n @Input() showFirstLastIcon: boolean = true;\n\n @Input() showPageLinks: boolean = true;\n\n @Input() defaultSortOrder: number = 1;\n\n @Input() sortMode: string = 'single';\n\n @Input() resetPageOnSort: boolean = true;\n\n @Input() customSort: boolean;\n\n @Input() selectionMode: string;\n\n @Output() selectionChange: EventEmitter<any> = new EventEmitter();\n\n @Input() contextMenuSelection: any;\n\n @Output() contextMenuSelectionChange: EventEmitter<any> = new EventEmitter();\n\n @Input() contextMenuSelectionMode: string = \"separate\";\n\n @Input() dataKey: string;\n\n @Input() metaKeySelection: boolean;\n\n @Input() compareSelectionBy: string = 'deepEquals';\n\n @Input() rowHover: boolean;\n\n @Input() loading: boolean;\n\n @Input() loadingIcon: string = 'pi pi-spinner';\n\n @Input() showLoader: boolean = true;\n\n @Input() scrollable: boolean;\n\n @Input() scrollHeight: string;\n\n @Input() virtualScroll: boolean;\n\n @Input() virtualScrollItemSize: number;\n\n @Input() virtualScrollOptions: ScrollerOptions;\n\n @Input() virtualScrollDelay: number = 150;\n\n @Input() frozenWidth: string;\n\n @Input() frozenColumns: any[];\n\n @Input() resizableColumns: boolean;\n\n @Input() columnResizeMode: string = 'fit';\n\n @Input() reorderableColumns: boolean;\n\n @Input() contextMenu: any;\n\n @Input() rowTrackBy: Function = (index: number, item: any) => item;\n\n @Input() filters: { [s: string]: FilterMetadata; } = {};\n\n @Input() globalFilterFields: string[];\n\n @Input() filterDelay: number = 300;\n\n @Input() filterMode: string = 'lenient';\n\n @Input() filterLocale: string;\n\n @Output() onFilter: EventEmitter<any> = new EventEmitter();\n\n @Output() onNodeExpand: EventEmitter<any> = new EventEmitter();\n\n @Output() onNodeCollapse: EventEmitter<any> = new EventEmitter();\n\n @Output() onPage: EventEmitter<any> = new EventEmitter();\n\n @Output() onSort: EventEmitter<any> = new EventEmitter();\n\n @Output() onLazyLoad: EventEmitter<any> = new EventEmitter();\n\n @Output() sortFunction: EventEmitter<any> = new EventEmitter();\n\n @Output() onColResize: EventEmitter<any> = new EventEmitter();\n\n @Output() onColReorder: EventEmitter<any> = new EventEmitter();\n\n @Output() onNodeSelect: EventEmitter<any> = new EventEmitter();\n\n @Output() onNodeUnselect: EventEmitter<any> = new EventEmitter();\n\n @Output() onContextMenuSelect: EventEmitter<any> = new EventEmitter();\n\n @Output() onHeaderCheckboxToggle: EventEmitter<any> = new EventEmitter();\n\n @Output() onEditInit: EventEmitter<any> = new EventEmitter();\n\n @Output() onEditComplete: EventEmitter<any> = new EventEmitter();\n\n @Output() onEditCancel: EventEmitter<any> = new EventEmitter();\n\n @ViewChild('container') containerViewChild: ElementRef;\n\n @ViewChild('resizeHelper') resizeHelperViewChild: ElementRef;\n\n @ViewChild('reorderIndicatorUp') reorderIndicatorUpViewChild: ElementRef;\n\n @ViewChild('reorderIndicatorDown') reorderIndicatorDownViewChild: ElementRef;\n\n @ViewChild('table') tableViewChild: ElementRef;\n\n @ViewChild('scrollableView') scrollableViewChild;\n\n @ViewChild('scrollableFrozenView') scrollableFrozenViewChild;\n\n @ContentChildren(PrimeTemplate) templates: QueryList<PrimeTemplate>;\n\n /* @deprecated */\n _virtualRowHeight: number = 28;\n @Input() get virtualRowHeight(): number {\n return this._virtualRowHeight;\n }\n set virtualRowHeight(val: number) {\n this._virtualRowHeight = val;\n console.warn(\"The virtualRowHeight property is deprecated, use virtualScrollItemSize property instead.\");\n }\n\n _value: TreeNode[] = [];\n\n serializedValue: any[];\n\n _totalRecords: number = 0;\n\n _multiSortMeta: SortMeta[];\n\n _sortField: string;\n\n _sortOrder: number = 1;\n\n filteredNodes: any[];\n\n filterTimeout: any;\n\n colGroupTemplate: TemplateRef<any>;\n\n captionTemplate: TemplateRef<any>;\n\n headerTemplate: TemplateRef<any>;\n\n bodyTemplate: TemplateRef<any>;\n\n loadingBodyTemplate: TemplateRef<any>;\n\n footerTemplate: TemplateRef<any>;\n\n summaryTemplate: TemplateRef<any>;\n\n emptyMessageTemplate: TemplateRef<any>;\n\n paginatorLeftTemplate: TemplateRef<any>;\n\n paginatorRightTemplate: TemplateRef<any>;\n\n paginatorDropdownItemTemplate: TemplateRef<any>;\n\n frozenHeaderTemplate: TemplateRef<any>;\n\n frozenBodyTemplate: TemplateRef<any>;\n\n frozenFooterTemplate: TemplateRef<any>;\n\n frozenColGroupTemplate: TemplateRef<any>;\n\n lastResizerHelperX: number;\n\n reorderIconWidth: number;\n\n reorderIconHeight: number;\n\n draggedColumn: any;\n\n dropPosition: number;\n\n preventSelectionSetterPropagation: boolean;\n\n _selection: any;\n\n selectionKeys: any = {};\n\n rowTouched: boolean;\n\n editingCell: Element;\n\n editingCellData: any;\n\n editingCellField: any;\n\n editingCellClick: boolean;\n\n documentEditListener: any;\n\n initialized: boolean;\n\n toggleRowIndex: number;\n\n ngOnInit() {\n if (this.lazy && this.lazyLoadOnInit && !this.virtualScroll) {\n this.onLazyLoad.emit(this.createLazyLoadMetadata());\n }\n this.initialized = true;\n }\n\n ngAfterContentInit() {\n this.templates.forEach((item) => {\n switch (item.getType()) {\n case 'caption':\n this.captionTemplate = item.template;\n break;\n\n case 'header':\n this.headerTemplate = item.template;\n break;\n\n case 'body':\n this.bodyTemplate = item.template;\n break;\n\n case 'loadingbody':\n this.loadingBodyTemplate = item.template;\n break;\n\n case 'footer':\n this.footerTemplate = item.template;\n break;\n\n case 'summary':\n this.summaryTemplate = item.template;\n break;\n\n case 'colgroup':\n this.colGroupTemplate = item.template;\n break;\n\n case 'emptymessage':\n this.emptyMessageTemplate = item.template;\n break;\n\n case 'paginatorleft':\n this.paginatorLeftTemplate = item.template;\n break;\n\n case 'paginatorright':\n this.paginatorRightTemplate = item.template;\n break;\n\n case 'paginatordropdownitem':\n this.paginatorDropdownItemTemplate = item.template;\n break;\n\n case 'frozenheader':\n this.frozenHeaderTemplate = item.template;\n break;\n\n case 'frozenbody':\n this.frozenBodyTemplate = item.template;\n break;\n\n case 'frozenfooter':\n this.frozenFooterTemplate = item.template;\n break;\n\n case 'frozencolgroup':\n this.frozenColGroupTemplate = item.template;\n break;\n }\n });\n }\n\n constructor(public el: ElementRef, public cd: ChangeDetectorRef, public zone: NgZone, public tableService: TreeTableService, public filterService: FilterService) {}\n\n ngOnChanges(simpleChange: SimpleChanges) {\n if (simpleChange.value) {\n this._value = simpleChange.value.currentValue;\n\n if (!this.lazy) {\n this.totalRecords = (this._value ? this._value.length : 0);\n\n if (this.sortMode == 'single' && this.sortField)\n this.sortSingle();\n else if (this.sortMode == 'multiple' && this.multiSortMeta)\n this.sortMultiple();\n else if (this.hasFilter()) //sort already filters\n this._filter();\n }\n\n this.updateSerializedValue();\n this.tableService.onUIUpdate(this.value);\n }\n\n if (simpleChange.sortField) {\n this._sortField = simpleChange.sortField.currentValue;\n\n //avoid triggering lazy load prior to lazy initialization at onInit\n if ( !this.lazy || this.initialized ) {\n if (this.sortMode === 'single') {\n this.sortSingle();\n }\n }\n }\n\n if (simpleChange.sortOrder) {\n this._sortOrder = simpleChange.sortOrder.currentValue;\n\n //avoid triggering lazy load prior to lazy initialization at onInit\n if ( !this.lazy || this.initialized ) {\n if (this.sortMode === 'single') {\n this.sortSingle();\n }\n }\n }\n\n if (simpleChange.multiSortMeta) {\n this._multiSortMeta = simpleChange.multiSortMeta.currentValue;\n if (this.sortMode === 'multiple') {\n this.sortMultiple();\n }\n }\n\n if (simpleChange.selection) {\n this._selection = simpleChange.selection.currentValue;\n\n if (!this.preventSelectionSetterPropagation) {\n this.updateSelectionKeys();\n this.tableService.onSelectionChange();\n }\n this.preventSelectionSetterPropagation = false;\n }\n }\n\n @Input() get value(): any[] {\n return this._value;\n }\n set value(val: any[]) {\n this._value = val;\n }\n\n updateSerializedValue() {\n this.serializedValue = [];\n\n if (this.paginator)\n this.serializePageNodes();\n else\n this.serializeNodes(null, this.filteredNodes||this.value, 0, true);\n }\n\n serializeNodes(parent, nodes, level, visible) {\n if (nodes && nodes.length) {\n for(let node of nodes) {\n node.parent = parent;\n const rowNode = {\n node: node,\n parent: parent,\n level: level,\n visible: visible && (parent ? parent.expanded : true)\n };\n this.serializedValue.push(rowNode);\n\n if (rowNode.visible && node.expanded) {\n this.serializeNodes(node, node.children, level + 1, rowNode.visible);\n }\n }\n }\n }\n\n serializePageNodes() {\n let data = this.filteredNodes || this.value;\n this.serializedValue = [];\n if (data && data.length) {\n const first = this.lazy ? 0 : this.first;\n\n for(let i = first; i < (first + this.rows); i++) {\n let node = data[i];\n if (node) {\n this.serializedValue.push({\n node: node,\n parent: null,\n level: 0,\n visible: true\n });\n\n this.serializeNodes(node, node.children, 1, true);\n }\n }\n }\n }\n\n @Input() get totalRecords(): number {\n return this._totalRecords;\n }\n set totalRecords(val: number) {\n this._totalRecords = val;\n this.tableService.onTotalRecordsChange(this._totalRecords);\n }\n\n @Input() get sortField(): string {\n return this._sortField;\n }\n\n set sortField(val: string) {\n this._sortField = val;\n }\n\n @Input() get sortOrder(): number {\n return this._sortOrder;\n }\n set sortOrder(val: number) {\n this._sortOrder = val;\n }\n\n @Input() get multiSortMeta(): SortMeta[] {\n return this._multiSortMeta;\n }\n\n set multiSortMeta(val: SortMeta[]) {\n this._multiSortMeta = val;\n }\n\n @Input() get selection(): any {\n return this._selection;\n }\n\n set selection(val: any) {\n this._selection = val;\n }\n\n updateSelectionKeys() {\n if (this.dataKey && this._selection) {\n this.selectionKeys = {};\n if (Array.isArray(this._selection)) {\n for(let node of this._selection) {\n this.selectionKeys[String(ObjectUtils.resolveFieldData(node.data, this.dataKey))] = 1;\n }\n }\n else {\n this.selectionKeys[String(ObjectUtils.resolveFieldData(this._selection.data, this.dataKey))] = 1;\n }\n }\n }\n\n onPageChange(event) {\n this.first = event.first;\n this.rows = event.rows;\n\n if (this.lazy)\n this.onLazyLoad.emit(this.createLazyLoadMetadata());\n else\n this.serializePageNodes();\n\n this.onPage.emit({\n first: this.first,\n rows: this.rows\n });\n\n this.tableService.onUIUpdate(this.value);\n\n if (this.scrollable) {\n this.resetScrollTop();\n }\n }\n\n sort(event) {\n let originalEvent = event.originalEvent;\n\n if (this.sortMode === 'single') {\n this._sortOrder = (this.sortField === event.field) ? this.sortOrder * -1 : this.defaultSortOrder;\n this._sortField = event.field;\n this.sortSingle();\n\n if (this.resetPageOnSort && this.scrollable) {\n this.resetScrollTop();\n }\n }\n if (this.sortMode === 'multiple') {\n let metaKey = originalEvent.metaKey || originalEvent.ctrlKey;\n let sortMeta = this.getSortMeta(event.field);\n\n if (sortMeta) {\n if (!metaKey) {\n this._multiSortMeta = [{ field: event.field, order: sortMeta.order * -1 }]\n\n if (this.resetPageOnSort && this.scrollable) {\n this.resetScrollTop();\n }\n }\n else {\n sortMeta.order = sortMeta.order * -1;\n }\n }\n else {\n if (!metaKey || !this.multiSortMeta) {\n this._multiSortMeta = [];\n\n if (this.resetPageOnSort && this.scrollable) {\n this.resetScrollTop();\n }\n }\n this.multiSortMeta.push({ field: event.field, order: this.defaultSortOrder });\n }\n\n this.sortMultiple();\n }\n }\n\n sortSingle() {\n if (this.sortField && this.sortOrder) {\n if (this.lazy) {\n this.onLazyLoad.emit(this.createLazyLoadMetadata());\n }\n else if (this.value) {\n this.sortNodes(this.value);\n\n if (this.hasFilter()) {\n this._filter();\n }\n }\n\n let sortMeta: SortMeta = {\n field: this.sortField,\n order: this.sortOrder\n };\n\n this.onSort.emit(sortMeta);\n this.tableService.onSort(sortMeta);\n this.updateSerializedValue();\n }\n }\n\n sortNodes(nodes) {\n if (!nodes || nodes.length === 0) {\n return;\n }\n\n if (this.customSort) {\n this.sortFunction.emit({\n data: nodes,\n mode: this.sortMode,\n field: this.sortField,\n order: this.sortOrder\n });\n }\n else {\n nodes.sort((node1, node2) => {\n let value1 = ObjectUtils.resolveFieldData(node1.data, this.sortField);\n let value2 = ObjectUtils.resolveFieldData(node2.data, this.sortField);\n let result = null;\n\n if (value1 == null && value2 != null)\n result = -1;\n else if (value1 != null && value2 == null)\n result = 1;\n else if (value1 == null && value2 == null)\n result = 0;\n else if (typeof value1 === 'string' && typeof value2 === 'string')\n result = value1.localeCompare(value2, undefined, {numeric: true});\n else\n result = (value1 < value2) ? -1 : (value1 > value2) ? 1 : 0;\n\n return (this.sortOrder * result);\n });\n }\n\n for(let node of nodes) {\n this.sortNodes(node.children);\n }\n }\n\n sortMultiple() {\n if (this.multiSortMeta) {\n if (this.lazy) {\n this.onLazyLoad.emit(this.createLazyLoadMetadata());\n }\n else if (this.value) {\n this.sortMultipleNodes(this.value);\n\n if (this.hasFilter()) {\n this._filter();\n }\n }\n\n this.onSort.emit({\n multisortmeta: this.multiSortMeta\n });\n this.updateSerializedValue();\n this.tableService.onSort(this.multiSortMeta);\n }\n }\n\n sortMultipleNodes(nodes) {\n if (!nodes || nodes.length === 0) {\n return;\n }\n\n if (this.customSort) {\n this.sortFunction.emit({\n data: this.value,\n mode: this.sortMode,\n multiSortMeta: this.multiSortMeta\n });\n }\n else {\n nodes.sort((node1, node2) => {\n return this.multisortField(node1, node2, this.multiSortMeta, 0);\n });\n }\n\n for(let node of nodes) {\n this.sortMultipleNodes(node.children);\n }\n }\n\n multisortField(node1, node2, multiSortMeta, index) {\n if (ObjectUtils.isEmpty(this.multiSortMeta) || ObjectUtils.isEmpty(multiSortMeta[index])) {\n return 0;\n }\n \n let value1 = ObjectUtils.resolveFieldData(node1.data, multiSortMeta[index].field);\n let value2 = ObjectUtils.resolveFieldData(node2.data, multiSortMeta[index].field);\n let result = null;\n\n if (value1 == null && value2 != null)\n result = -1;\n else if (value1 != null && value2 == null)\n result = 1;\n else if (value1 == null && value2 == null)\n result = 0;\n if (typeof value1 == 'string' || value1 instanceof String) {\n if (value1.localeCompare && (value1 != value2)) {\n return (multiSortMeta[index].order * value1.localeCompare(value2, undefined, {numeric: true}));\n }\n }\n else {\n result = (value1 < value2) ? -1 : 1;\n }\n\n if (value1 == value2) {\n return (multiSortMeta.length - 1) > (index) ? (this.multisortField(node1, node2, multiSortMeta, index + 1)) : 0;\n }\n\n return (multiSortMeta[index].order * result);\n }\n\n getSortMeta(field: string) {\n if (this.multiSortMeta && this.multiSortMeta.length) {\n for (let i = 0; i < this.multiSortMeta.length; i++) {\n if (this.multiSortMeta[i].field === field) {\n return this.multiSortMeta[i];\n }\n }\n }\n\n return null;\n }\n\n isSorted(field: string) {\n if (this.sortMode === 'single') {\n return (this.sortField && this.sortField === field);\n }\n else if (this.sortMode === 'multiple') {\n let sorted = false;\n if (this.multiSortMeta) {\n for(let i = 0; i < this.multiSortMeta.length; i++) {\n if (this.multiSortMeta[i].field == field) {\n sorted = true;\n break;\n }\n }\n }\n return sorted;\n }\n }\n\n createLazyLoadMetadata(): any {\n return {\n first: this.first,\n rows: this.rows,\n sortField: this.sortField,\n sortOrder: this.sortOrder,\n filters: this.filters,\n globalFilter: this.filters && this.filters['global'] ? this.filters['global'].value : null,\n multiSortMeta: this.multiSortMeta,\n forceUpdate: () => this.cd.detectChanges()\n };\n }\n\n onLazyItemLoad(event) {\n this.onLazyLoad.emit({\n ...this.createLazyLoadMetadata(),\n ...event,\n rows: event.last - event.first\n });\n }\n\n public resetScrollTop() {\n if (this.virtualScroll)\n this.scrollToVirtualIndex(0);\n else\n this.scrollTo({top: 0});\n }\n\n public scrollToVirtualIndex(index: number) {\n if (this.scrollableViewChild) {\n this.scrollableViewChild.scrollToVirtualIndex(index);\n }\n\n if (this.scrollableFrozenViewChild) {\n this.scrollableFrozenViewChild.scrollToVirtualIndex(index);\n }\n }\n\n public scrollTo(options) {\n if (this.scrollableViewChild) {\n this.scrollableViewChild.scrollTo(options);\n }\n\n if (this.scrollableFrozenViewChild) {\n this.scrollableFrozenViewChild.scrollTo(options);\n }\n }\n\n isEmpty() {\n let data = this.filteredNodes||this.value;\n return data == null || data.length == 0;\n }\n\n getBlockableElement(): HTMLElement {\n return this.el.nativeElement.children[0];\n }\n\n onColumnResizeBegin(event) {\n let containerLeft = DomHandler.getOffset(this.containerViewChild.nativeElement).left;\n this.lastResizerHelperX = (event.pageX - containerLeft + this.containerViewChild.nativeElement.scrollLeft);\n event.preventDefault();\n }\n\n onColumnResize(event) {\n let containerLeft = DomHandler.getOffset(this.containerViewChild.nativeElement).left;\n DomHandler.addClass(this.containerViewChild.nativeElement, 'p-unselectable-text');\n this.resizeHelperViewChild.nativeElement.style.height = this.containerViewChild.nativeElement.offsetHeight + 'px';\n this.resizeHelperViewChild.nativeElement.style.top = 0 + 'px';\n this.resizeHelperViewChild.nativeElement.style.left = (event.pageX - containerLeft + this.containerViewChild.nativeElement.scrollLeft) + 'px';\n\n this.resizeHelperViewChild.nativeElement.style.display = 'block';\n }\n\n onColumnResizeEnd(event, column) {\n let delta = this.resizeHelperViewChild.nativeElement.offsetLeft - this.lastResizerHelperX;\n let columnWidth = column.offsetWidth;\n let newColumnWidth = columnWidth + delta;\n let minWidth = column.style.minWidth || 15;\n\n if (columnWidth + delta > parseInt(minWidth)) {\n if (this.columnResizeMode === 'fit') {\n let nextColumn = column.nextElementSibling;\n while (!nextColumn.offsetParent) {\n nextColumn = nextColumn.nextElementSibling;\n }\n\n if (nextColumn) {\n let nextColumnWidth = nextColumn.offsetWidth - delta;\n let nextColumnMinWidth = nextColumn.style.minWidth || 15;\n\n if (newColumnWidth > 15 && nextColumnWidth > parseInt(nextColumnMinWidth)) {\n if (this.scrollable) {\n let scrollableView = this.findParentScrollableView(column);\n let scrollableBodyTable = DomHandler.findSingle(scrollableView, '.p-treetable-scrollable-body table') || DomHandler.findSingle(scrollableView, '.p-scroller-viewport table');\n let scrollableHeaderTable = DomHandler.findSingle(scrollableView, 'table.p-treetable-scrollable-header-table');\n let scrollableFooterTable = DomHandler.findSingle(scrollableView, 'table.p-treetable-scrollable-footer-table');\n let resizeColumnIndex = DomHandler.index(column);\n\n this.resizeColGroup(scrollableHeaderTable, resizeColumnIndex, newColumnWidth, nextColumnWidth);\n this.resizeColGroup(scrollableBodyTable, resizeColumnIndex, newColumnWidth, nextColumnWidth);\n this.resizeColGroup(scrollableFooterTable, resizeColumnIndex, newColumnWidth, nextColumnWidth);\n }\n else {\n column.style.width = newColumnWidth + 'px';\n if (nextColumn) {\n nextColumn.style.width = nextColumnWidth + 'px';\n }\n }\n }\n }\n }\n else if (this.columnResizeMode === 'expand') {\n if (this.scrollable) {\n let scrollableView = this.findParentScrollableView(column);\n let scrollableBody = DomHandler.findSingle(scrollableView, '.p-treetable-scrollable-body') || DomHandler.findSingle(scrollableView, '.p-scroller-viewport');\n let scrollableHeader = DomHandler.findSingle(scrollableView, '.p-treetable-scrollable-header');\n let scrollableFooter = DomHandler.findSingle(scrollableView, '.p-treetable-scrollable-footer');\n let scrollableBodyTable = DomHandler.findSingle(scrollableView, '.p-treetable-scrollable-body table') || DomHandler.findSingle(scrollableView, '.p-scroller-viewport table');\n let scrollableHeaderTable = DomHandler.findSingle(scrollableView, 'table.p-treetable-scrollable-header-table');\n let scrollableFooterTable = DomHandler.findSingle(scrollableView, 'table.p-treetable-scrollable-footer-table');\n scrollableBodyTable.style.width = scrollableBodyTable.offsetWidth + delta + 'px';\n scrollableHeaderTable.style.width = scrollableHeaderTable.offsetWidth + delta + 'px';\n if (scrollableFooterTable) {\n scrollableFooterTable.style.width = scrollableFooterTable.offsetWidth + delta + 'px';\n }\n let resizeColumnIndex = DomHandler.index(column);\n\n const scrollableBodyTableWidth = column ? scrollableBodyTable.offsetWidth + delta : newColumnWidth;\n const scrollableHeaderTableWidth = column ? scrollableHeaderTable.offsetWidth + delta : newColumnWidth;\n const isContainerInViewport = this.containerViewChild.nativeElement.offsetWidth >= scrollableBodyTableWidth;\n\n let setWidth = (container, table, width, isContainerInViewport) => {\n if (container && table) {\n container.style.width = isContainerInViewport ? width + DomHandler.calculateScrollbarWidth(scrollableBody) + 'px' : 'auto'\n table.style.width = width + 'px';\n }\n };\n\n setWidth(scrollableBody, scrollableBodyTable, scrollableBodyTableWidth, isContainerInViewport);\n setWidth(scrollableHeader, scrollableHeaderTable, scrollableHeaderTableWidth, isContainerInViewport);\n setWidth(scrollableFooter, scrollableFooterTable, scrollableHeaderTableWidth, isContainerInViewport);\n\n this.resizeColGroup(scrollableHeaderTable, resizeColumnIndex, newColumnWidth, null);\n this.resizeColGroup(scrollableBodyTable, resizeColumnIndex, newColumnWidth, null);\n this.resizeColGroup(scrollableFooterTable, resizeColumnIndex, newColumnWidth, null);\n }\n else {\n this.tableViewChild.nativeElement.style.width = this.tableViewChild.nativeElement.offsetWidth + delta + 'px';\n column.style.width = newColumnWidth + 'px';\n let containerWidth = this.tableViewChild.nativeElement.style.width;\n this.containerViewChild.nativeElement.style.width = containerWidth + 'px';\n }\n }\n\n this.onColResize.emit({\n element: column,\n delta: delta\n });\n }\n\n this.resizeHelperViewChild.nativeElement.style.display = 'none';\n DomHandler.removeClass(this.containerViewChild.nativeElement, 'p-unselectable-text');\n }\n\n findParentScrollableView(column) {\n if (column) {\n let parent = column.parentElement;\n while (parent && !DomHandler.hasClass(parent, 'p-treetable-scrollable-view')) {\n parent = parent.parentElement;\n }\n\n return parent;\n }\n else {\n return null;\n }\n }\n\n resizeColGroup(table, resizeColumnIndex, newColumnWidth, nextColumnWidth) {\n if (table) {\n let colGroup = table.children[0].nodeName === 'COLGROUP' ? table.children[0] : null;\n\n if (colGroup) {\n let col = colGroup.children[resizeColumnIndex];\n let nextCol = col.nextElementSibling;\n col.style.width = newColumnWidth + 'px';\n\n if (nextCol && nextColumnWidth) {\n nextCol.style.width = nextColumnWidth + 'px';\n }\n }\n else {\n throw \"Scrollable tables require a colgroup to support resizable columns\";\n }\n }\n }\n\n onColumnDragStart(event, columnElement) {\n this.reorderIconWidth = DomHandler.getHiddenElementOuterWidth(this.reorderIndicatorUpViewChild.nativeElement);\n this.reorderIconHeight = DomHandler.getHiddenElementOuterHeight(this.reorderIndicatorDownViewChild.nativeElement);\n this.draggedColumn = columnElement;\n event.dataTransfer.setData('text', 'b'); // For firefox\n }\n\n onColumnDragEnter(event, dropHeader) {\n if (this.reorderableColumns && this.draggedColumn && dropHeader) {\n event.preventDefault();\n let containerOffset = DomHandler.getOffset(this.containerViewChild.nativeElement);\n let dropHeaderOffset = DomHandler.getOffset(dropHeader);\n\n if (this.draggedColumn != dropHeader) {\n let targetLeft = dropHeaderOffset.left - containerOffset.left;\n let targetTop = containerOffset.top - dropHeaderOffset.top;\n let columnCenter = dropHeaderOffset.left + dropHeader.offsetWidth / 2;\n\n this.reorderIndicatorUpViewChild.nativeElement.style.top = dropHeaderOffset.top - containerOffset.top - (this.reorderIconHeight - 1) + 'px';\n this.reorderIndicatorDownViewChild.nativeElement.style.top = dropHeaderOffset.top - containerOffset.top + dropHeader.offsetHeight + 'px';\n\n if (event.pageX > columnCenter) {\n this.reorderIndicatorUpViewChild.nativeElement.style.left = (targetLeft + dropHeader.offsetWidth - Math.ceil(this.reorderIconWidth / 2)) + 'px';\n this.reorderIndicatorDownViewChild.nativeElement.style.left = (targetLeft + dropHeader.offsetWidth - Math.ceil(this.reorderIconWidth / 2)) + 'px';\n this.dropPosition = 1;\n }\n else {\n this.reorderIndicatorUpViewChild.nativeElement.style.left = (targetLeft - Math.ceil(this.reorderIconWidth / 2)) + 'px';\n this.reorderIndicatorDownViewChild.nativeElement.style.left = (targetLeft - Math.ceil(this.reorderIconWidth / 2)) + 'px';\n this.dropPosition = -1;\n }\n\n this.reorderIndicatorUpViewChild.nativeElement.style.display = 'block';\n this.reorderIndicatorDownViewChild.nativeElement.style.display = 'block';\n }\n else {\n event.dataTransfer.dropEffect = 'none';\n }\n }\n }\n\n onColumnDragLeave(event) {\n if (this.reorderableColumns && this.draggedColumn) {\n event.preventDefault();\n this.reorderIndicatorUpViewChild.nativeElement.style.display = 'none';\n this.reorderIndicatorDownViewChild.nativeElement.style.display = 'none';\n }\n }\n\n onColumnDrop(event, dropColumn) {\n event.preventDefault();\n if (this.draggedColumn) {\n let dragIndex = DomHandler.indexWithinGroup(this.draggedColumn, 'ttreorderablecolumn');\n let dropIndex = DomHandler.indexWithinGroup(dropColumn, 'ttreorderablecolumn');\n let allowDrop = (dragIndex != dropIndex);\n if (allowDrop && ((dropIndex - dragIndex == 1 && this.dropPosition === -1) || (dragIndex - dropIndex == 1 && this.dropPosition === 1))) {\n allowDrop = false;\n }\n\n if (allowDrop && ((dropIndex < dragIndex && this.dropPosition === 1))) {\n dropIndex = dropIndex + 1;\n }\n\n if (allowDrop && ((dropIndex > dragIndex && this.dropPosition === -1))) {\n dropIndex = dropIndex - 1;\n }\n\n if (allowDrop) {\n ObjectUtils.reorderArray(this.columns, dragIndex, dropIndex);\n\n this.onColReorder.emit({\n dragIndex: dragIndex,\n dropIndex: dropIndex,\n columns: this.columns\n });\n }\n\n this.reorderIndicatorUpViewChild.nativeElement.style.display = 'none';\n this.reorderIndicatorDownViewChild.nativeElement.style.display = 'none';\n this.draggedColumn.draggable = false;\n this.draggedColumn = null;\n this.dropPosition = null;\n }\n }\n\n handleRowClick(event) {\n let targetNode = (<HTMLElement> event.originalEvent.target).nodeName;\n if (targetNode == 'INPUT' || targetNode == 'BUTTON' || targetNode == 'A' || (DomHandler.hasClass(event.originalEvent.target, 'p-clickable'))) {\n return;\n }\n\n if (this.selectionMode) {\n this.preventSelectionSetterPropagation = true;\n let rowNode = event.rowNode;\n let selected = this.isSelected(rowNode.node);\n let metaSelection = this.rowTouched ? false : this.metaKeySelection;\n let dataKeyValue = this.dataKey ? String(ObjectUtils.resolveFieldData(rowNode.node.data, this.dataKey)) : null;\n\n if (metaSelection) {\n let metaKey = event.originalEvent.metaKey||event.originalEvent.ctrlKey;\n\n if (selected && metaKey) {\n if (this.isSingleSelectionMode()) {\n this._selection = null;\n this.selectionKeys = {};\n this.selectionChange.emit(null);\n }\n else {\n let selectionIndex = this.findIndexInSelection(rowNode.node);\n this._selection = this.selection.filter((val,i) => i != selectionIndex);\n this.selectionChange.emit(this.selection);\n if (dataKeyValue) {\n delete this.selectionKeys[dataKeyValue];\n }\n }\n\n this.onNodeUnselect.emit({originalEvent: event.originalEvent, node: rowNode.node, type: 'row'});\n }\n else {\n if (this.isSingleSelectionMode()) {\n this._selection = rowNode.node;\n this.selectionChange.emit(rowNode.node);\n if (dataKeyValue) {\n this.selectionKeys = {};\n this.selectionKeys[dataKeyValue] = 1;\n }\n }\n else if (this.isMultipleSelectionMode()) {\n if (metaKey) {\n this._selection = this.selection||[];\n }\n else {\n this._selection = [];\n this.selectionKeys = {};\n }\n\n this._selection = [...this.selection, rowNode.node];\n this.selectionChange.emit(this.selection);\n if (dataKeyValue) {\n this.selectionKeys[dataKeyValue] = 1;\n }\n }\n\n this.onNodeSelect.emit({originalEvent: event.originalEvent, node: rowNode.node, type: 'row', index: event.rowIndex});\n }\n }\n else {\n if (this.selectionMode === 'single') {\n if (selected) {\n this._selection = null;\n this.selectionKeys = {};\n this.selectionChange.emit(this.selection);\n this.onNodeUnselect.emit({ originalEvent: event.originalEvent, node: rowNode.node, type: 'row' });\n }\n else {\n this._selection = rowNode.node;\n this.selectionChange.emit(this.selection);\n this.onNodeSelect.emit({ originalEvent: event.originalEvent, node: rowNode.node, type: 'row', index: event.rowIndex });\n if (dataKeyValue) {\n this.selectionKeys = {};\n this.selectionKeys[dataKeyValue] = 1;\n }\n }\n }\n else if (this.selectionMode === 'multiple') {\n if (selected) {\n let selectionIndex = this.findIndexInSelection(rowNode.node);\n this._selection = this.selection.filter((val, i) => i != selectionIndex);\n this.selectionChange.emit(this.selection);\n this.onNodeUnselect.emit({ originalEvent: event.originalEvent, node: rowNode.node, type: 'row' });\n if (dataKeyValue) {\n delete this.selectionKeys[dataKeyValue];\n }\n }\n else {\n this._selection = this.selection ? [...this.selection, rowNode.node] : [rowNode.node];\n this.selectionChange.emit(this.selection);\n this.onNodeSelect.emit({ originalEvent: event.originalEvent, node: rowNode.node, type: 'row', index: event.rowIndex });\n if (dataKeyValue) {\n this.selectionKeys[dataKeyValue] = 1;\n }\n }\n }\n }\n\n this.tableService.onSelectionChange();\n }\n\n this.rowTouched = false;\n }\n\n handleRowTouchEnd(event) {\n this.rowTouched = true;\n }\n\n handleRowRightClick(event) {\n if (this.contextMenu) {\n const node = event.rowNode.node;\n\n if (this.contextMenuSelectionMode === 'separate') {\n this.contextMenuSelection = node;\n this.contextMenuSelectionChange.emit(node);\n this.onContextMenuSelect.emit({originalEvent: event.originalEvent, node: node});\n this.contextMenu.show(event.originalEvent);\n this.tableService.onContextMenu(node);\n }\n else if (this.contextMenuSelectionMode === 'joint') {\n this.preventSelectionSetterPropagation = true;\n let selected = this.isSelected(node);\n let dataKeyValue = this.dataKey ? String(ObjectUtils.resolveFieldData(node.data, this.dataKey)) : null;\n\n if (!selected) {\n if (this.isSingleSelectionMode()) {\n this.selection = node;\n this.selectionChange.emit(node);\n }\n else if (this.isMultipleSelectionMode()) {\n this.selection = [node];\n this.selectionChange.emit(this.selection);\n }\n\n if (dataKeyValue) {\n this.selectionKeys[dataKeyValue] = 1;\n }\n }\n\n this.contextMenu.show(event.originalEvent);\n this.onContextMenuSelect.emit({originalEvent: event.originalEvent, node: node});\n }\n }\n }\n\n toggleNodeWithCheckbox(event) {\n this.selection = this.selection||[];\n this.preventSelectionSetterPropagation = true;\n let node = event.rowNode.node;\n let selected = this.isSelected(node);\n\n if (selected) {\n this.propagateSelectionDown(node, false);\n if (event.rowNode.parent) {\n this.propagateSelectionUp(node.parent, false);\n }\n this.selectionChange.emit(this.selection);\n this.onNodeUnselect.emit({originalEvent: event, node: node});\n }\n els