<template> <tbody :ref="bodyRef" class="p-datatable-tbody" role="rowgroup" :style="bodyStyle"> <template v-if="!empty"> <template v-for="(rowData, index) of value"> <tr v-if="templates['groupheader'] && rowGroupMode === 'subheader' && shouldRenderRowGroupHeader(value, rowData, getRowIndex(index))" :key="getRowKey(rowData, getRowIndex(index)) + '_subheader'" class="p-rowgroup-header" :style="rowGroupHeaderStyle" role="row" > <td :colspan="columnsLength - 1"> <button v-if="expandableRowGroups" class="p-row-toggler p-link" @click="onRowGroupToggle($event, rowData)" type="button"> <component v-if="templates['rowgrouptogglericon']" :is="templates['rowgrouptogglericon']" :expanded="isRowGroupExpanded(rowData)" /> <template v-else> <span v-if="isRowGroupExpanded(rowData) && expandedRowIcon" :class="['p-row-toggler-icon', expandedRowIcon]" /> <ChevronDownIcon v-else-if="isRowGroupExpanded(rowData) && !expandedRowIcon" class="p-row-toggler-icon" /> <span v-else-if="!isRowGroupExpanded(rowData) && collapsedRowIcon" :class="['p-row-toggler-icon', collapsedRowIcon]" /> <ChevronRightIcon v-else-if="!isRowGroupExpanded(rowData) && !collapsedRowIcon" class="p-row-toggler-icon" /> </template> </button> <component :is="templates['groupheader']" :data="rowData" :index="getRowIndex(index)" /> </td> </tr> <tr v-if="expandableRowGroups ? isRowGroupExpanded(rowData) : true" :key="getRowKey(rowData, getRowIndex(index))" :class="getRowClass(rowData)" :style="getRowStyle(rowData)" :tabindex="setRowTabindex(index)" role="row" :aria-selected="selectionMode ? isSelected(rowData) : null" @click="onRowClick($event, rowData, getRowIndex(index))" @dblclick="onRowDblClick($event, rowData, getRowIndex(index))" @contextmenu="onRowRightClick($event, rowData, getRowIndex(index))" @touchend="onRowTouchEnd($event)" @keydown="onRowKeyDown($event, rowData, getRowIndex(index))" @mousedown="onRowMouseDown($event)" @dragstart="onRowDragStart($event, getRowIndex(index))" @dragover="onRowDragOver($event, getRowIndex(index))" @dragleave="onRowDragLeave($event)" @dragend="onRowDragEnd($event)" @drop="onRowDrop($event)" > <template v-for="(col, i) of columns"> <DTBodyCell v-if="shouldRenderBodyCell(value, col, getRowIndex(index))" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i" :rowData="rowData" :column="col" :rowIndex="getRowIndex(index)" :index="i" :selected="isSelected(rowData)" :frozenRow="frozenRow" :rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(value, col, getRowIndex(index)) : null" :editMode="editMode" :editing="editMode === 'row' && isRowEditing(rowData)" :editingMeta="editingMeta" :responsiveLayout="responsiveLayout" :virtualScrollerContentProps="virtualScrollerContentProps" :ariaControls="expandedRowId + '_' + index + '_expansion'" :name="nameAttributeSelector" :isRowExpanded="isRowExpanded(rowData)" :expandedRowIcon="expandedRowIcon" :collapsedRowIcon="collapsedRowIcon" @radio-change="onRadioChange($event)" @checkbox-change="onCheckboxChange($event)" @row-toggle="onRowToggle($event)" @cell-edit-init="onCellEditInit($event)" @cell-edit-complete="onCellEditComplete($event)" @cell-edit-cancel="onCellEditCancel($event)" @row-edit-init="onRowEditInit($event)" @row-edit-save="onRowEditSave($event)" @row-edit-cancel="onRowEditCancel($event)" @editing-meta-change="onEditingMetaChange" /> </template> </tr> <tr v-if="templates['expansion'] && expandedRows && isRowExpanded(rowData)" :key="getRowKey(rowData, getRowIndex(index)) + '_expansion'" :id="expandedRowId + '_' + index + '_expansion'" class="p-datatable-row-expansion" role="row"> <td :colspan="columnsLength"> <component :is="templates['expansion']" :data="rowData" :index="getRowIndex(index)" /> </td> </tr> <tr v-if="templates['groupfooter'] && rowGroupMode === 'subheader' && shouldRenderRowGroupFooter(value, rowData, getRowIndex(index))" :key="getRowKey(rowData, getRowIndex(index)) + '_subfooter'" class="p-rowgroup-footer" role="row"> <td :colspan="columnsLength - 1"> <component :is="templates['groupfooter']" :data="rowData" :index="getRowIndex(index)" /> </td> </tr> </template> </template> <tr v-else class="p-datatable-emptymessage" role="row"> <td :colspan="columnsLength"> <component v-if="templates.empty" :is="templates.empty" /> </td> </tr> </tbody> </template> <script> import ChevronDownIcon from 'primevue/icons/chevrondown'; import ChevronRightIcon from 'primevue/icons/chevronright'; import { DomHandler, ObjectUtils, UniqueComponentId } from 'primevue/utils'; import BodyCell from './BodyCell.vue'; export default { name: 'TableBody', emits: [ 'rowgroup-toggle', 'row-click', 'row-dblclick', 'row-rightclick', 'row-touchend', 'row-keydown', 'row-mousedown', 'row-dragstart', 'row-dragover', 'row-dragleave', 'row-dragend', 'row-drop', 'row-toggle', 'radio-change', 'checkbox-change', 'cell-edit-init', 'cell-edit-complete', 'cell-edit-cancel', 'row-edit-init', 'row-edit-save', 'row-edit-cancel', 'editing-meta-change' ], props: { value: { type: Array, default: null }, columns: { type: null, default: null }, frozenRow: { type: Boolean, default: false }, empty: { type: Boolean, default: false }, rowGroupMode: { type: String, default: null }, groupRowsBy: { type: [Array, String, Function], default: null }, expandableRowGroups: { type: Boolean, default: false }, expandedRowGroups: { type: Array, default: null }, first: { type: Number, default: 0 }, dataKey: { type: String, default: null }, expandedRowIcon: { type: String, default: null }, collapsedRowIcon: { type: String, default: null }, expandedRows: { type: Array, default: null }, expandedRowKeys: { type: null, default: null }, selection: { type: [Array, Object], default: null }, selectionKeys: { type: null, default: null }, selectionMode: { type: String, default: null }, contextMenu: { type: Boolean, default: false }, contextMenuSelection: { type: Object, default: null }, rowClass: { type: null, default: null }, rowStyle: { type: null, default: null }, editMode: { type: String, default: null }, compareSelectionBy: { type: String, default: 'deepEquals' }, editingRows: { type: Array, default: null }, editingRowKeys: { type: null, default: null }, editingMeta: { type: Object, default: null }, templates: { type: null, default: null }, scrollable: { type: Boolean, default: false }, responsiveLayout: { type: String, default: 'stack' }, virtualScrollerContentProps: { type: Object, default: null }, isVirtualScrollerDisabled: { type: Boolean, default: false } }, data() { return { rowGroupHeaderStyleObject: {}, tabindexArray: [], isARowSelected: false }; }, mounted() { if (this.frozenRow) { this.updateFrozenRowStickyPosition(); } if (this.scrollable && this.rowGroupMode === 'subheader') { this.updateFrozenRowGroupHeaderStickyPosition(); } }, updated() { if (this.frozenRow) { this.updateFrozenRowStickyPosition(); } if (this.scrollable && this.rowGroupMode === 'subheader') { this.updateFrozenRowGroupHeaderStickyPosition(); } }, methods: { columnProp(col, prop) { return ObjectUtils.getVNodeProp(col, prop); }, shouldRenderRowGroupHeader(value, rowData, i) { let currentRowFieldData = ObjectUtils.resolveFieldData(rowData, this.groupRowsBy); let prevRowData = value[i - 1]; if (prevRowData) { let previousRowFieldData = ObjectUtils.resolveFieldData(prevRowData, this.groupRowsBy); return currentRowFieldData !== previousRowFieldData; } else { return true; } }, getRowKey(rowData, index) { return this.dataKey ? ObjectUtils.resolveFieldData(rowData, this.dataKey) : this.getRowIndex(index); }, getRowIndex(index) { const getItemOptions = this.getVirtualScrollerProp('getItemOptions'); return getItemOptions ? getItemOptions(index).index : this.first + index; }, getRowStyle(rowData) { if (this.rowStyle) { return this.rowStyle(rowData); } }, getRowClass(rowData) { let rowStyleClass = []; if (this.selectionMode) { rowStyleClass.push('p-selectable-row'); } if (this.selection) { rowStyleClass.push({ 'p-highlight': this.isSelected(rowData) }); } if (this.contextMenuSelection) { rowStyleClass.push({ 'p-highlight-contextmenu': this.isSelectedWithContextMenu(rowData) }); } if (this.rowClass) { let rowClassValue = this.rowClass(rowData); if (rowClassValue) { rowStyleClass.push(rowClassValue); } } return rowStyleClass; }, shouldRenderRowGroupFooter(value, rowData, i) { if (this.expandableRowGroups && !this.isRowGroupExpanded(rowData)) { return false; } else { let currentRowFieldData = ObjectUtils.resolveFieldData(rowData, this.groupRowsBy); let nextRowData = value[i + 1]; if (nextRowData) { let nextRowFieldData = ObjectUtils.resolveFieldData(nextRowData, this.groupRowsBy); return currentRowFieldData !== nextRowFieldData; } else { return true; } } }, shouldRenderBodyCell(value, column, i) { if (this.rowGroupMode) { if (this.rowGroupMode === 'subheader') { return this.groupRowsBy !== this.columnProp(column, 'field'); } else if (this.rowGroupMode === 'rowspan') { if (this.isGrouped(column)) { let prevRowData = value[i - 1]; if (prevRowData) { let currentRowFieldData = ObjectUtils.resolveFieldData(value[i], this.columnProp(column, 'field')); let previousRowFieldData = ObjectUtils.resolveFieldData(prevRowData, this.columnProp(column, 'field')); return currentRowFieldData !== previousRowFieldData; } else { return true; } } else { return true; } } } else { return !this.columnProp(column, 'hidden'); } }, calculateRowGroupSize(value, column, index) { if (this.isGrouped(column)) { let currentRowFieldData = ObjectUtils.resolveFieldData(value[index], this.columnProp(column, 'field')); let nextRowFieldData = currentRowFieldData; let groupRowSpan = 0; while (currentRowFieldData === nextRowFieldData) { groupRowSpan++; let nextRowData = value[++index]; if (nextRowData) { nextRowFieldData = ObjectUtils.resolveFieldData(nextRowData, this.columnProp(column, 'field')); } else { break; } } return groupRowSpan === 1 ? null : groupRowSpan; } else { return null; } }, isGrouped(column) { if (this.groupRowsBy && this.columnProp(column, 'field')) { if (Array.isArray(this.groupRowsBy)) return this.groupRowsBy.indexOf(column.props.field) > -1; else return this.groupRowsBy === column.props.field; } else { return false; } }, isRowEditing(rowData) { if (rowData && this.editingRows) { if (this.dataKey) return this.editingRowKeys ? this.editingRowKeys[ObjectUtils.resolveFieldData(rowData, this.dataKey)] !== undefined : false; else return this.findIndex(rowData, this.editingRows) > -1; } return false; }, isRowExpanded(rowData) { if (rowData && this.expandedRows) { if (this.dataKey) return this.expandedRowKeys ? this.expandedRowKeys[ObjectUtils.resolveFieldData(rowData, this.dataKey)] !== undefined : false; else return this.findIndex(rowData, this.expandedRows) > -1; } return false; }, isRowGroupExpanded(rowData) { if (this.expandableRowGroups && this.expandedRowGroups) { let groupFieldValue = ObjectUtils.resolveFieldData(rowData, this.groupRowsBy); return this.expandedRowGroups.indexOf(groupFieldValue) > -1; } return false; }, isSelected(rowData) { if (rowData && this.selection) { if (this.dataKey) { return this.selectionKeys ? this.selectionKeys[ObjectUtils.resolveFieldData(rowData, this.dataKey)] !== undefined : false; } else { if (this.selection instanceof Array) return this.findIndexInSelection(rowData) > -1; else return this.equals(rowData, this.selection); } } return false; }, isSelectedWithContextMenu(rowData) { if (rowData && this.contextMenuSelection) { return this.equals(rowData, this.contextMenuSelection, this.dataKey); } return false; }, findIndexInSelection(rowData) { return this.findIndex(rowData, this.selection); }, findIndex(rowData, collection) { let index = -1; if (collection && collection.length) { for (let i = 0; i < collection.length; i++) { if (this.equals(rowData, collection[i])) { index = i; break; } } } return index; }, equals(data1, data2) { return this.compareSelectionBy === 'equals' ? data1 === data2 : ObjectUtils.equals(data1, data2, this.dataKey); }, onRowGroupToggle(event, data) { this.$emit('rowgroup-toggle', { originalEvent: event, data: data }); }, onRowClick(event, rowData, rowIndex) { this.$emit('row-click', { originalEvent: event, data: rowData, index: rowIndex }); }, onRowDblClick(event, rowData, rowIndex) { this.$emit('row-dblclick', { originalEvent: event, data: rowData, index: rowIndex }); }, onRowRightClick(event, rowData, rowIndex) { this.$emit('row-rightclick', { originalEvent: event, data: rowData, index: rowIndex }); }, onRowTouchEnd(event) { this.$emit('row-touchend', event); }, onRowKeyDown(event, rowData, rowIndex) { this.$emit('row-keydown', { originalEvent: event, data: rowData, index: rowIndex }); }, onRowMouseDown(event) { this.$emit('row-mousedown', event); }, onRowDragStart(event, rowIndex) { this.$emit('row-dragstart', { originalEvent: event, index: rowIndex }); }, onRowDragOver(event, rowIndex) { this.$emit('row-dragover', { originalEvent: event, index: rowIndex }); }, onRowDragLeave(event) { this.$emit('row-dragleave', event); }, onRowDragEnd(event) { this.$emit('row-dragend', event); }, onRowDrop(event) { this.$emit('row-drop', event); }, onRowToggle(event) { this.$emit('row-toggle', event); }, onRadioChange(event) { this.$emit('radio-change', event); }, onCheckboxChange(event) { this.$emit('checkbox-change', event); }, onCellEditInit(event) { this.$emit('cell-edit-init', event); }, onCellEditComplete(event) { this.$emit('cell-edit-complete', event); }, onCellEditCancel(event) { this.$emit('cell-edit-cancel', event); }, onRowEditInit(event) { this.$emit('row-edit-init', event); }, onRowEditSave(event) { this.$emit('row-edit-save', event); }, onRowEditCancel(event) { this.$emit('row-edit-cancel', event); }, onEditingMetaChange(event) { this.$emit('editing-meta-change', event); }, updateFrozenRowStickyPosition() { this.$el.style.top = DomHandler.getOuterHeight(this.$el.previousElementSibling) + 'px'; }, updateFrozenRowGroupHeaderStickyPosition() { let tableHeaderHeight = DomHandler.getOuterHeight(this.$el.previousElementSibling); this.rowGroupHeaderStyleObject.top = tableHeaderHeight + 'px'; }, getVirtualScrollerProp(option, options) { options = options || this.virtualScrollerContentProps; return options ? options[option] : null; }, bodyRef(el) { // For VirtualScroller const contentRef = this.getVirtualScrollerProp('contentRef'); contentRef && contentRef(el); }, setRowTabindex(index) { if (this.selection === null && (this.selectionMode === 'single' || this.selectionMode === 'multiple')) { return index === 0 ? 0 : -1; } return -1; } }, computed: { columnsLength() { let hiddenColLength = 0; this.columns.forEach((column) => { if (this.columnProp(column, 'selectionMode') === 'single') hiddenColLength--; if (this.columnProp(column, 'hidden')) hiddenColLength++; }); return this.columns ? this.columns.length - hiddenColLength : 0; }, rowGroupHeaderStyle() { if (this.scrollable) { return { top: this.rowGroupHeaderStyleObject.top }; } return null; }, bodyStyle() { return this.getVirtualScrollerProp('contentStyle'); }, expandedRowId() { return UniqueComponentId(); }, nameAttributeSelector() { return UniqueComponentId(); } }, components: { DTBodyCell: BodyCell, ChevronDownIcon: ChevronDownIcon, ChevronRightIcon: ChevronRightIcon } }; </script>