<template> <div :class="containerClass" data-scrollselectors=".p-treetable-scrollable-body" role="table"> <div v-if="loading" class="p-treetable-loading"> <div class="p-treetable-loading-overlay p-component-overlay"> <slot name="loadingicon"> <component :is="loadingIcon ? 'span' : 'SpinnerIcon'" spin :class="['p-treetable-loading-icon', loadingIcon]" /> </slot> </div> </div> <div v-if="$slots.header" class="p-treetable-header"> <slot name="header"></slot> </div> <TTPaginator v-if="paginatorTop" :rows="d_rows" :first="d_first" :totalRecords="totalRecordsLength" :pageLinkSize="pageLinkSize" :template="paginatorTemplate" :rowsPerPageOptions="rowsPerPageOptions" :currentPageReportTemplate="currentPageReportTemplate" class="p-paginator-top" @page="onPage($event)" :alwaysShow="alwaysShowPaginator" > <template v-if="$slots.paginatorstart" #start> <slot name="paginatorstart"></slot> </template> <template v-if="$slots.paginatorend" #end> <slot name="paginatorend"></slot> </template> <template v-if="$slots.paginatorfirstpagelinkicon" #firstpagelinkicon> <slot name="paginatorfirstpagelinkicon"></slot> </template> <template v-if="$slots.paginatorprevpagelinkicon" #prevpagelinkicon> <slot name="paginatorprevpagelinkicon"></slot> </template> <template v-if="$slots.paginatornextpagelinkicon" #nextpagelinkicon> <slot name="paginatornextpagelinkicon"></slot> </template> <template v-if="$slots.paginatorlastpagelinkicon" #lastpagelinkicon> <slot name="paginatorlastpagelinkicon"></slot> </template> </TTPaginator> <div class="p-treetable-wrapper" :style="{ maxHeight: scrollHeight }"> <table ref="table" role="table" v-bind="tableProps"> <thead class="p-treetable-thead" role="rowgroup"> <tr role="row"> <template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i"> <TTHeaderCell v-if="!columnProp(col, 'hidden')" :column="col" :resizableColumns="resizableColumns" :sortField="d_sortField" :sortOrder="d_sortOrder" :multiSortMeta="d_multiSortMeta" :sortMode="sortMode" @column-click="onColumnHeaderClick" @column-resizestart="onColumnResizeStart" ></TTHeaderCell> </template> </tr> <tr v-if="hasColumnFilter()"> <template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i"> <th v-if="!columnProp(col, 'hidden')" :class="getFilterColumnHeaderClass(col)" :style="[columnProp(col, 'style'), columnProp(col, 'filterHeaderStyle')]"> <component v-if="col.children && col.children.filter" :is="col.children.filter" :column="col" /> </th> </template> </tr> </thead> <tbody class="p-treetable-tbody" role="rowgroup"> <template v-if="!empty"> <TTRow v-for="(node, index) of dataToRender" :key="node.key" :columns="columns" :node="node" :level="0" :expandedKeys="d_expandedKeys" :indentation="indentation" :selectionMode="selectionMode" :selectionKeys="selectionKeys" :ariaSetSize="dataToRender.length" :ariaPosInset="index + 1" :tabindex="setTabindex(node, index)" :templates="$slots" @node-toggle="onNodeToggle" @node-click="onNodeClick" @checkbox-change="onCheckboxChange" ></TTRow> </template> <tr v-else class="p-treetable-emptymessage"> <td :colspan="columns.length"> <slot name="empty"></slot> </td> </tr> </tbody> <tfoot v-if="hasFooter" class="p-treetable-tfoot" role="rowgroup"> <tr role="row"> <template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i"> <TTFooterCell v-if="!columnProp(col, 'hidden')" :column="col"></TTFooterCell> </template> </tr> </tfoot> </table> </div> <TTPaginator v-if="paginatorBottom" :rows="d_rows" :first="d_first" :totalRecords="totalRecordsLength" :pageLinkSize="pageLinkSize" :template="paginatorTemplate" :rowsPerPageOptions="rowsPerPageOptions" :currentPageReportTemplate="currentPageReportTemplate" class="p-paginator-bottom" @page="onPage($event)" :alwaysShow="alwaysShowPaginator" > <template v-if="$slots.paginatorstart" #start> <slot name="paginatorstart"></slot> </template> <template v-if="$slots.paginatorend" #end> <slot name="paginatorend"></slot> </template> <template v-if="$slots.paginatorfirstpagelinkicon" #firstpagelinkicon> <slot name="paginatorfirstpagelinkicon"></slot> </template> <template v-if="$slots.paginatorprevpagelinkicon" #prevpagelinkicon> <slot name="paginatorprevpagelinkicon"></slot> </template> <template v-if="$slots.paginatornextpagelinkicon" #nextpagelinkicon> <slot name="paginatornextpagelinkicon"></slot> </template> <template v-if="$slots.paginatorlastpagelinkicon" #lastpagelinkicon> <slot name="paginatorlastpagelinkicon"></slot> </template> </TTPaginator> <div v-if="$slots.footer" class="p-treetable-footer"> <slot name="footer"></slot> </div> <div ref="resizeHelper" class="p-column-resizer-helper p-highlight" style="display: none"></div> </div> </template> <script> import { FilterService } from 'primevue/api'; import SpinnerIcon from 'primevue/icons/spinner'; import Paginator from 'primevue/paginator'; import { DomHandler, ObjectUtils } from 'primevue/utils'; import FooterCell from './FooterCell.vue'; import HeaderCell from './HeaderCell.vue'; import TreeTableRow from './TreeTableRow.vue'; export default { name: 'TreeTable', emits: [ 'node-expand', 'node-collapse', 'update:expandedKeys', 'update:selectionKeys', 'node-select', 'node-unselect', 'update:first', 'update:rows', 'page', 'update:sortField', 'update:sortOrder', 'update:multiSortMeta', 'sort', 'filter', 'column-resize-end' ], props: { value: { type: null, default: null }, expandedKeys: { type: null, default: null }, selectionKeys: { type: null, default: null }, selectionMode: { type: String, default: null }, metaKeySelection: { type: Boolean, default: true }, rows: { type: Number, default: 0 }, first: { type: Number, default: 0 }, totalRecords: { type: Number, default: 0 }, paginator: { type: Boolean, default: false }, paginatorPosition: { type: String, default: 'bottom' }, alwaysShowPaginator: { type: Boolean, default: true }, paginatorTemplate: { type: String, default: 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown' }, pageLinkSize: { type: Number, default: 5 }, rowsPerPageOptions: { type: Array, default: null }, currentPageReportTemplate: { type: String, default: '({currentPage} of {totalPages})' }, lazy: { type: Boolean, default: false }, loading: { type: Boolean, default: false }, loadingIcon: { type: String, default: undefined }, rowHover: { type: Boolean, default: false }, autoLayout: { type: Boolean, default: false }, sortField: { type: [String, Function], default: null }, sortOrder: { type: Number, default: null }, defaultSortOrder: { type: Number, default: 1 }, multiSortMeta: { type: Array, default: null }, sortMode: { type: String, default: 'single' }, removableSort: { type: Boolean, default: false }, filters: { type: Object, default: null }, filterMode: { type: String, default: 'lenient' }, filterLocale: { type: String, default: undefined }, resizableColumns: { type: Boolean, default: false }, columnResizeMode: { type: String, default: 'fit' }, indentation: { type: Number, default: 1 }, showGridlines: { type: Boolean, default: false }, scrollable: { type: Boolean, default: false }, scrollDirection: { type: String, default: 'vertical' }, scrollHeight: { type: String, default: null }, responsiveLayout: { type: String, default: null }, tableProps: { type: Object, default: null } }, documentColumnResizeListener: null, documentColumnResizeEndListener: null, lastResizeHelperX: null, resizeColumnElement: null, data() { return { d_expandedKeys: this.expandedKeys || {}, d_first: this.first, d_rows: this.rows, d_sortField: this.sortField, d_sortOrder: this.sortOrder, d_multiSortMeta: this.multiSortMeta ? [...this.multiSortMeta] : [], hasASelectedNode: false }; }, watch: { expandedKeys(newValue) { this.d_expandedKeys = newValue; }, first(newValue) { this.d_first = newValue; }, rows(newValue) { this.d_rows = newValue; }, sortField(newValue) { this.d_sortField = newValue; }, sortOrder(newValue) { this.d_sortOrder = newValue; }, multiSortMeta(newValue) { this.d_multiSortMeta = newValue; } }, mounted() { if (this.scrollable && this.scrollDirection !== 'vertical') { this.updateScrollWidth(); } }, updated() { if (this.scrollable && this.scrollDirection !== 'vertical') { this.updateScrollWidth(); } }, methods: { columnProp(col, prop) { return ObjectUtils.getVNodeProp(col, prop); }, onNodeToggle(node) { const key = node.key; if (this.d_expandedKeys[key]) { delete this.d_expandedKeys[key]; this.$emit('node-collapse', node); } else { this.d_expandedKeys[key] = true; this.$emit('node-expand', node); } this.d_expandedKeys = { ...this.d_expandedKeys }; this.$emit('update:expandedKeys', this.d_expandedKeys); }, onNodeClick(event) { if (this.rowSelectionMode && event.node.selectable !== false) { const metaSelection = event.nodeTouched ? false : this.metaKeySelection; const _selectionKeys = metaSelection ? this.handleSelectionWithMetaKey(event) : this.handleSelectionWithoutMetaKey(event); this.$emit('update:selectionKeys', _selectionKeys); } }, handleSelectionWithMetaKey(event) { const originalEvent = event.originalEvent; const node = event.node; const metaKey = originalEvent.metaKey || originalEvent.ctrlKey; const selected = this.isNodeSelected(node); let _selectionKeys; if (selected && metaKey) { if (this.isSingleSelectionMode()) { _selectionKeys = {}; } else { _selectionKeys = { ...this.selectionKeys }; delete _selectionKeys[node.key]; } this.$emit('node-unselect', node); } else { if (this.isSingleSelectionMode()) { _selectionKeys = {}; } else if (this.isMultipleSelectionMode()) { _selectionKeys = !metaKey ? {} : this.selectionKeys ? { ...this.selectionKeys } : {}; } _selectionKeys[node.key] = true; this.$emit('node-select', node); } return _selectionKeys; }, handleSelectionWithoutMetaKey(event) { const node = event.node; const selected = this.isNodeSelected(node); let _selectionKeys; if (this.isSingleSelectionMode()) { if (selected) { _selectionKeys = {}; this.$emit('node-unselect', node); } else { _selectionKeys = {}; _selectionKeys[node.key] = true; this.$emit('node-select', node); } } else { if (selected) { _selectionKeys = { ...this.selectionKeys }; delete _selectionKeys[node.key]; this.$emit('node-unselect', node); } else { _selectionKeys = this.selectionKeys ? { ...this.selectionKeys } : {}; _selectionKeys[node.key] = true; this.$emit('node-select', node); } } return _selectionKeys; }, onCheckboxChange(event) { this.$emit('update:selectionKeys', event.selectionKeys); if (event.check) this.$emit('node-select', event.node); else this.$emit('node-unselect', event.node); }, isSingleSelectionMode() { return this.selectionMode === 'single'; }, isMultipleSelectionMode() { return this.selectionMode === 'multiple'; }, onPage(event) { this.d_first = event.first; this.d_rows = event.rows; let pageEvent = this.createLazyLoadEvent(event); pageEvent.pageCount = event.pageCount; pageEvent.page = event.page; this.$emit('update:first', this.d_first); this.$emit('update:rows', this.d_rows); this.$emit('page', pageEvent); }, resetPage() { this.d_first = 0; this.$emit('update:first', this.d_first); }, getFilterColumnHeaderClass(column) { return [ 'p-filter-column', this.columnProp(column, 'filterHeaderClass'), { 'p-frozen-column': this.columnProp(column, 'frozen') } ]; }, onColumnHeaderClick(e) { let event = e.originalEvent; let column = e.column; if (this.columnProp(column, 'sortable')) { const targetNode = event.target; const columnField = this.columnProp(column, 'sortField') || this.columnProp(column, 'field'); if ( DomHandler.hasClass(targetNode, 'p-sortable-column') || DomHandler.hasClass(targetNode, 'p-column-title') || DomHandler.hasClass(targetNode, 'p-sortable-column-icon') || DomHandler.hasClass(targetNode.parentElement, 'p-sortable-column-icon') ) { DomHandler.clearSelection(); if (this.sortMode === 'single') { if (this.d_sortField === columnField) { if (this.removableSort && this.d_sortOrder * -1 === this.defaultSortOrder) { this.d_sortOrder = null; this.d_sortField = null; } else { this.d_sortOrder = this.d_sortOrder * -1; } } else { this.d_sortOrder = this.defaultSortOrder; this.d_sortField = columnField; } this.$emit('update:sortField', this.d_sortField); this.$emit('update:sortOrder', this.d_sortOrder); this.resetPage(); } else if (this.sortMode === 'multiple') { let metaKey = event.metaKey || event.ctrlKey; if (!metaKey) { this.d_multiSortMeta = this.d_multiSortMeta.filter((meta) => meta.field === columnField); } this.addMultiSortField(columnField); this.$emit('update:multiSortMeta', this.d_multiSortMeta); } this.$emit('sort', this.createLazyLoadEvent(event)); } } }, addMultiSortField(field) { let index = this.d_multiSortMeta.findIndex((meta) => meta.field === field); if (index >= 0) { if (this.removableSort && this.d_multiSortMeta[index].order * -1 === this.defaultSortOrder) this.d_multiSortMeta.splice(index, 1); else this.d_multiSortMeta[index] = { field: field, order: this.d_multiSortMeta[index].order * -1 }; } else { this.d_multiSortMeta.push({ field: field, order: this.defaultSortOrder }); } this.d_multiSortMeta = [...this.d_multiSortMeta]; }, sortSingle(nodes) { return this.sortNodesSingle(nodes); }, sortNodesSingle(nodes) { let _nodes = [...nodes]; _nodes.sort((node1, node2) => { const value1 = ObjectUtils.resolveFieldData(node1.data, this.d_sortField); const value2 = ObjectUtils.resolveFieldData(node2.data, this.d_sortField); let result = null; if (value1 == null && value2 != null) result = -1; else if (value1 != null && value2 == null) result = 1; else if (value1 == null && value2 == null) result = 0; else if (typeof value1 === 'string' && typeof value2 === 'string') result = value1.localeCompare(value2, undefined, { numeric: true }); else result = value1 < value2 ? -1 : value1 > value2 ? 1 : 0; return this.d_sortOrder * result; }); return _nodes; }, sortMultiple(nodes) { return this.sortNodesMultiple(nodes); }, sortNodesMultiple(nodes) { let _nodes = [...nodes]; _nodes.sort((node1, node2) => { return this.multisortField(node1, node2, 0); }); return _nodes; }, multisortField(node1, node2, index) { const value1 = ObjectUtils.resolveFieldData(node1.data, this.d_multiSortMeta[index].field); const value2 = ObjectUtils.resolveFieldData(node2.data, this.d_multiSortMeta[index].field); let result = null; if (value1 == null && value2 != null) result = -1; else if (value1 != null && value2 == null) result = 1; else if (value1 == null && value2 == null) result = 0; else { if (value1 === value2) { return this.d_multiSortMeta.length - 1 > index ? this.multisortField(node1, node2, index + 1) : 0; } else { if ((typeof value1 === 'string' || value1 instanceof String) && (typeof value2 === 'string' || value2 instanceof String)) return this.d_multiSortMeta[index].order * value1.localeCompare(value2, undefined, { numeric: true }); else result = value1 < value2 ? -1 : 1; } } return this.d_multiSortMeta[index].order * result; }, filter(value) { let filteredNodes = []; const strict = this.filterMode === 'strict'; for (let node of value) { let copyNode = { ...node }; let localMatch = true; let globalMatch = false; for (let j = 0; j < this.columns.length; j++) { let col = this.columns[j]; let filterField = this.columnProp(col, 'field'); //local if (Object.prototype.hasOwnProperty.call(this.filters, this.columnProp(col, 'field'))) { let filterMatchMode = this.columnProp(col, 'filterMatchMode') || 'startsWith'; let filterValue = this.filters[this.columnProp(col, 'field')]; let filterConstraint = FilterService.filters[filterMatchMode]; let paramsWithoutNode = { filterField, filterValue, filterConstraint, strict }; if ( (strict && !(this.findFilteredNodes(copyNode, paramsWithoutNode) || this.isFilterMatched(copyNode, paramsWithoutNode))) || (!strict && !(this.isFilterMatched(copyNode, paramsWithoutNode) || this.findFilteredNodes(copyNode, paramsWithoutNode))) ) { localMatch = false; } if (!localMatch) { break; } } //global if (this.hasGlobalFilter() && !globalMatch) { let copyNodeForGlobal = { ...copyNode }; let filterValue = this.filters['global']; let filterConstraint = FilterService.filters['contains']; let globalFilterParamsWithoutNode = { filterField, filterValue, filterConstraint, strict }; if ( (strict && (this.findFilteredNodes(copyNodeForGlobal, globalFilterParamsWithoutNode) || this.isFilterMatched(copyNodeForGlobal, globalFilterParamsWithoutNode))) || (!strict && (this.isFilterMatched(copyNodeForGlobal, globalFilterParamsWithoutNode) || this.findFilteredNodes(copyNodeForGlobal, globalFilterParamsWithoutNode))) ) { globalMatch = true; copyNode = copyNodeForGlobal; } } } let matches = localMatch; if (this.hasGlobalFilter()) { matches = localMatch && globalMatch; } if (matches) { filteredNodes.push(copyNode); } } let filterEvent = this.createLazyLoadEvent(event); filterEvent.filteredValue = filteredNodes; this.$emit('filter', filterEvent); return filteredNodes; }, findFilteredNodes(node, paramsWithoutNode) { if (node) { let matched = false; if (node.children) { let childNodes = [...node.children]; node.children = []; for (let childNode of childNodes) { let copyChildNode = { ...childNode }; if (this.isFilterMatched(copyChildNode, paramsWithoutNode)) { matched = true; node.children.push(copyChildNode); } } } if (matched) { return true; } } }, isFilterMatched(node, { filterField, filterValue, filterConstraint, strict }) { let matched = false; let dataFieldValue = ObjectUtils.resolveFieldData(node.data, filterField); if (filterConstraint(dataFieldValue, filterValue, this.filterLocale)) { matched = true; } if (!matched || (strict && !this.isNodeLeaf(node))) { matched = this.findFilteredNodes(node, { filterField, filterValue, filterConstraint, strict }) || matched; } return matched; }, isNodeSelected(node) { return this.selectionMode && this.selectionKeys ? this.selectionKeys[node.key] === true : false; }, isNodeLeaf(node) { return node.leaf === false ? false : !(node.children && node.children.length); }, createLazyLoadEvent(event) { let filterMatchModes; if (this.hasFilters()) { filterMatchModes = {}; this.columns.forEach((col) => { if (this.columnProp(col, 'field')) { filterMatchModes[col.props.field] = this.columnProp(col, 'filterMatchMode'); } }); } return { originalEvent: event, first: this.d_first, rows: this.d_rows, sortField: this.d_sortField, sortOrder: this.d_sortOrder, multiSortMeta: this.d_multiSortMeta, filters: this.filters, filterMatchModes: filterMatchModes }; }, onColumnResizeStart(event) { let containerLeft = DomHandler.getOffset(this.$el).left; this.resizeColumnElement = event.target.parentElement; this.columnResizing = true; this.lastResizeHelperX = event.pageX - containerLeft + this.$el.scrollLeft; this.bindColumnResizeEvents(); }, onColumnResize(event) { let containerLeft = DomHandler.getOffset(this.$el).left; DomHandler.addClass(this.$el, 'p-unselectable-text'); this.$refs.resizeHelper.style.height = this.$el.offsetHeight + 'px'; this.$refs.resizeHelper.style.top = 0 + 'px'; this.$refs.resizeHelper.style.left = event.pageX - containerLeft + this.$el.scrollLeft + 'px'; this.$refs.resizeHelper.style.display = 'block'; }, onColumnResizeEnd() { let delta = this.$refs.resizeHelper.offsetLeft - this.lastResizeHelperX; let columnWidth = this.resizeColumnElement.offsetWidth; let newColumnWidth = columnWidth + delta; let minWidth = this.resizeColumnElement.style.minWidth || 15; if (columnWidth + delta > parseInt(minWidth, 10)) { if (this.columnResizeMode === 'fit') { let nextColumn = this.resizeColumnElement.nextElementSibling; let nextColumnWidth = nextColumn.offsetWidth - delta; if (newColumnWidth > 15 && nextColumnWidth > 15) { if (!this.scrollable) { this.resizeColumnElement.style.width = newColumnWidth + 'px'; if (nextColumn) { nextColumn.style.width = nextColumnWidth + 'px'; } } else { this.resizeTableCells(newColumnWidth, nextColumnWidth); } } } else if (this.columnResizeMode === 'expand') { this.$refs.table.style.width = this.$refs.table.offsetWidth + delta + 'px'; if (!this.scrollable) this.resizeColumnElement.style.width = newColumnWidth + 'px'; else this.resizeTableCells(newColumnWidth); } this.$emit('column-resize-end', { element: this.resizeColumnElement, delta: delta }); } this.$refs.resizeHelper.style.display = 'none'; this.resizeColumn = null; DomHandler.removeClass(this.$el, 'p-unselectable-text'); this.unbindColumnResizeEvents(); }, resizeTableCells(newColumnWidth, nextColumnWidth) { let colIndex = DomHandler.index(this.resizeColumnElement); let children = this.$refs.table.children; for (let child of children) { for (let row of child.children) { let resizeCell = row.children[colIndex]; resizeCell.style.flex = '0 0 ' + newColumnWidth + 'px'; if (this.columnResizeMode === 'fit') { let nextCell = resizeCell.nextElementSibling; if (nextCell) { nextCell.style.flex = '0 0 ' + nextColumnWidth + 'px'; } } } } }, bindColumnResizeEvents() { if (!this.documentColumnResizeListener) { this.documentColumnResizeListener = document.addEventListener('mousemove', (event) => { if (this.columnResizing) { this.onColumnResize(event); } }); } if (!this.documentColumnResizeEndListener) { this.documentColumnResizeEndListener = document.addEventListener('mouseup', () => { if (this.columnResizing) { this.columnResizing = false; this.onColumnResizeEnd(); } }); } }, unbindColumnResizeEvents() { if (this.documentColumnResizeListener) { document.removeEventListener('document', this.documentColumnResizeListener); this.documentColumnResizeListener = null; } if (this.documentColumnResizeEndListener) { document.removeEventListener('document', this.documentColumnResizeEndListener); this.documentColumnResizeEndListener = null; } }, onColumnKeyDown(event, col) { if (event.code === 'Enter' && event.currentTarget.nodeName === 'TH' && DomHandler.hasClass(event.currentTarget, 'p-sortable-column')) { this.onColumnHeaderClick(event, col); } }, hasColumnFilter() { if (this.columns) { for (let col of this.columns) { if (col.children && col.children.filter) { return true; } } } return false; }, hasFilters() { return this.filters && Object.keys(this.filters).length > 0 && this.filters.constructor === Object; }, hasGlobalFilter() { return this.filters && Object.prototype.hasOwnProperty.call(this.filters, 'global'); }, updateScrollWidth() { this.$refs.table.style.width = this.$refs.table.scrollWidth + 'px'; }, getItemLabel(node) { return node.data.name; }, setTabindex(node, index) { if (this.isNodeSelected(node)) { this.hasASelectedNode = true; return 0; } if (this.selectionMode) { if (!this.isNodeSelected(node) && index === 0 && !this.hasASelectedNode) return 0; } else if (!this.selectionMode && index === 0) { return 0; } return -1; } }, computed: { containerClass() { return [ 'p-treetable p-component', { 'p-treetable-hoverable-rows': this.rowHover || this.rowSelectionMode, 'p-treetable-auto-layout': this.autoLayout, 'p-treetable-resizable': this.resizableColumns, 'p-treetable-resizable-fit': this.resizableColumns && this.columnResizeMode === 'fit', 'p-treetable-gridlines': this.showGridlines, 'p-treetable-scrollable': this.scrollable, 'p-treetable-scrollable-vertical': this.scrollable && this.scrollDirection === 'vertical', 'p-treetable-scrollable-horizontal': this.scrollable && this.scrollDirection === 'horizontal', 'p-treetable-scrollable-both': this.scrollable && this.scrollDirection === 'both', 'p-treetable-flex-scrollable': this.scrollable && this.scrollHeight === 'flex', 'p-treetable-responsive-scroll': this.responsiveLayout === 'scroll' } ]; }, columns() { let cols = []; let children = this.$slots.default(); children.forEach((child) => { if (child.children && child.children instanceof Array) cols = [...cols, ...child.children]; else if (child.type.name === 'Column') cols.push(child); }); return cols; }, processedData() { if (this.lazy) { return this.value; } else { if (this.value && this.value.length) { let data = this.value; if (this.sorted) { if (this.sortMode === 'single') data = this.sortSingle(data); else if (this.sortMode === 'multiple') data = this.sortMultiple(data); } if (this.hasFilters()) { data = this.filter(data); } return data; } else { return null; } } }, dataToRender() { const data = this.processedData; if (this.paginator) { const first = this.lazy ? 0 : this.d_first; return data.slice(first, first + this.d_rows); } else { return data; } }, empty() { const data = this.processedData; return !data || data.length === 0; }, sorted() { return this.d_sortField || (this.d_multiSortMeta && this.d_multiSortMeta.length > 0); }, hasFooter() { let hasFooter = false; for (let col of this.columns) { if (this.columnProp(col, 'footer') || (col.children && col.children.footer)) { hasFooter = true; break; } } return hasFooter; }, paginatorTop() { return this.paginator && (this.paginatorPosition !== 'bottom' || this.paginatorPosition === 'both'); }, paginatorBottom() { return this.paginator && (this.paginatorPosition !== 'top' || this.paginatorPosition === 'both'); }, singleSelectionMode() { return this.selectionMode && this.selectionMode === 'single'; }, multipleSelectionMode() { return this.selectionMode && this.selectionMode === 'multiple'; }, rowSelectionMode() { return this.singleSelectionMode || this.multipleSelectionMode; }, totalRecordsLength() { if (this.lazy) { return this.totalRecords; } else { const data = this.processedData; return data ? data.length : 0; } } }, components: { TTRow: TreeTableRow, TTPaginator: Paginator, TTHeaderCell: HeaderCell, TTFooterCell: FooterCell, SpinnerIcon: SpinnerIcon } }; </script> <style> .p-treetable { position: relative; } .p-treetable table { border-collapse: collapse; width: 100%; table-layout: fixed; } .p-treetable .p-sortable-column { cursor: pointer; user-select: none; } .p-treetable-responsive-scroll > .p-treetable-wrapper { overflow-x: auto; } .p-treetable-responsive-scroll > .p-treetable-wrapper > table, .p-treetable-auto-layout > .p-treetable-wrapper > table { table-layout: auto; } .p-treetable-hoverable-rows .p-treetable-tbody > tr { cursor: pointer; } .p-treetable-toggler { cursor: pointer; user-select: none; display: inline-flex; align-items: center; justify-content: center; vertical-align: middle; overflow: hidden; position: relative; } .p-treetable-toggler + .p-checkbox { vertical-align: middle; } .p-treetable-toggler + .p-checkbox + span { vertical-align: middle; } /* Resizable */ .p-treetable-resizable > .p-treetable-wrapper { overflow-x: auto; } .p-treetable-resizable .p-treetable-thead > tr > th, .p-treetable-resizable .p-treetable-tfoot > tr > td, .p-treetable-resizable .p-treetable-tbody > tr > td { overflow: hidden; } .p-treetable-resizable .p-resizable-column:not(.p-frozen-column) { background-clip: padding-box; position: relative; } .p-treetable-resizable-fit .p-resizable-column:last-child .p-column-resizer { display: none; } .p-treetable .p-column-resizer { display: block; position: absolute !important; top: 0; right: 0; margin: 0; width: 0.5rem; height: 100%; padding: 0px; cursor: col-resize; border: 1px solid transparent; } .p-treetable .p-column-resizer-helper { width: 1px; position: absolute; z-index: 10; display: none; } .p-treetable .p-treetable-loading-overlay { position: absolute; display: flex; align-items: center; justify-content: center; z-index: 2; } /* Scrollable */ .p-treetable-scrollable .p-treetable-wrapper { position: relative; overflow: auto; } .p-treetable-scrollable .p-treetable-table { display: block; } .p-treetable-scrollable .p-treetable-thead, .p-treetable-scrollable .p-treetable-tbody, .p-treetable-scrollable .p-treetable-tfoot { display: block; } .p-treetable-scrollable .p-treetable-thead > tr, .p-treetable-scrollable .p-treetable-tbody > tr, .p-treetable-scrollable .p-treetable-tfoot > tr { display: flex; flex-wrap: nowrap; width: 100%; } .p-treetable-scrollable .p-treetable-thead > tr > th, .p-treetable-scrollable .p-treetable-tbody > tr > td, .p-treetable-scrollable .p-treetable-tfoot > tr > td { display: flex; flex: 1 1 0; align-items: center; } .p-treetable-scrollable .p-treetable-thead { position: sticky; top: 0; z-index: 1; } .p-treetable-scrollable .p-treetable-tfoot { position: sticky; bottom: 0; z-index: 1; } .p-treetable-scrollable .p-frozen-column { position: sticky; background: inherit; } .p-treetable-scrollable th.p-frozen-column { z-index: 1; } .p-treetable-scrollable-both .p-treetable-thead > tr > th, .p-treetable-scrollable-both .p-treetable-tbody > tr > td, .p-treetable-scrollable-both .p-treetable-tfoot > tr > td, .p-treetable-scrollable-horizontal .p-treetable-thead > tr > th .p-treetable-scrollable-horizontal .p-treetable-tbody > tr > td, .p-treetable-scrollable-horizontal .p-treetable-tfoot > tr > td { flex: 0 0 auto; } .p-treetable-flex-scrollable { display: flex; flex-direction: column; height: 100%; } .p-treetable-flex-scrollable .p-treetable-wrapper { display: flex; flex-direction: column; flex: 1; height: 100%; } </style>