mirror of
https://github.com/primefaces/primevue.git
synced 2025-05-09 17:02:38 +00:00
* DataTable performance improvement (1) * Refactor on DT performance * Refactor * Refactor * Update RowExpansionDoc.vue * Update RowExpansionDoc.vue * Update RowExpansionDoc.vue
This commit is contained in:
parent
f6c5cda987
commit
fb1fb60eab
7 changed files with 674 additions and 467 deletions
575
components/lib/datatable/BodyRow.vue
Normal file
575
components/lib/datatable/BodyRow.vue
Normal file
|
@ -0,0 +1,575 @@
|
|||
<template>
|
||||
<template v-if="!empty">
|
||||
<tr v-if="templates['groupheader'] && rowGroupMode === 'subheader' && shouldRenderRowGroupHeader" :class="cx('rowGroupHeader')" :style="rowGroupHeaderStyle" role="row" v-bind="ptm('rowGroupHeader')">
|
||||
<td :colspan="columnsLength - 1" v-bind="{ ...getColumnPT('bodycell'), ...ptm('rowGroupHeaderCell') }">
|
||||
<button v-if="expandableRowGroups" :class="cx('rowGroupToggler')" @click="onRowGroupToggle" type="button" v-bind="ptm('rowGroupToggler')">
|
||||
<component v-if="templates['rowgrouptogglericon']" :is="templates['rowgrouptogglericon']" :expanded="isRowGroupExpanded" />
|
||||
<template v-else>
|
||||
<span v-if="isRowGroupExpanded && expandedRowIcon" :class="[cx('rowGroupTogglerIcon'), expandedRowIcon]" v-bind="ptm('rowGroupTogglerIcon')" />
|
||||
<ChevronDownIcon v-else-if="isRowGroupExpanded && !expandedRowIcon" :class="cx('rowGroupTogglerIcon')" v-bind="ptm('rowGroupTogglerIcon')" />
|
||||
<span v-else-if="!isRowGroupExpanded && collapsedRowIcon" :class="[cx('rowGroupTogglerIcon'), collapsedRowIcon]" v-bind="ptm('rowGroupTogglerIcon')" />
|
||||
<ChevronRightIcon v-else-if="!isRowGroupExpanded && !collapsedRowIcon" :class="cx('rowGroupTogglerIcon')" v-bind="ptm('rowGroupTogglerIcon')" />
|
||||
</template>
|
||||
</button>
|
||||
<component :is="templates['groupheader']" :data="rowData" :index="rowIndex" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="expandableRowGroups ? isRowGroupExpanded : true"
|
||||
:class="rowClasses"
|
||||
:style="rowStyles"
|
||||
:tabindex="rowTabindex"
|
||||
role="row"
|
||||
:aria-selected="selectionMode ? isSelected : null"
|
||||
@click="onRowClick"
|
||||
@dblclick="onRowDblClick"
|
||||
@contextmenu="onRowRightClick"
|
||||
@touchend="onRowTouchEnd"
|
||||
@keydown.self="onRowKeyDown"
|
||||
@mousedown="onRowMouseDown"
|
||||
@dragstart="onRowDragStart"
|
||||
@dragover="onRowDragOver"
|
||||
@dragleave="onRowDragLeave"
|
||||
@dragend="onRowDragEnd"
|
||||
@drop="onRowDrop"
|
||||
v-bind="getBodyRowPTOptions('bodyRow')"
|
||||
:data-p-selectable-row="selectionMode ? true : false"
|
||||
:data-p-highlight="selection && isSelected"
|
||||
:data-p-highlight-contextmenu="contextMenuSelection && isSelectedWithContextMenu"
|
||||
>
|
||||
<template v-for="(col, i) of columns">
|
||||
<DTBodyCell
|
||||
v-if="shouldRenderBodyCell(col)"
|
||||
:key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i"
|
||||
:rowData="rowData"
|
||||
:column="col"
|
||||
:rowIndex="rowIndex"
|
||||
:index="i"
|
||||
:selected="isSelected"
|
||||
:frozenRow="frozenRow"
|
||||
:rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(col) : null"
|
||||
:editMode="editMode"
|
||||
:editing="editMode === 'row' && isRowEditing"
|
||||
:editingMeta="editingMeta"
|
||||
:responsiveLayout="responsiveLayout"
|
||||
:virtualScrollerContentProps="virtualScrollerContentProps"
|
||||
:ariaControls="expandedRowId + '_' + rowIndex + '_expansion'"
|
||||
:name="nameAttributeSelector"
|
||||
:isRowExpanded="d_rowExpanded"
|
||||
:expandedRowIcon="expandedRowIcon"
|
||||
:collapsedRowIcon="collapsedRowIcon"
|
||||
@radio-change="onRadioChange"
|
||||
@checkbox-change="onCheckboxChange"
|
||||
@row-toggle="onRowToggle"
|
||||
@cell-edit-init="onCellEditInit"
|
||||
@cell-edit-complete="onCellEditComplete"
|
||||
@cell-edit-cancel="onCellEditCancel"
|
||||
@row-edit-init="onRowEditInit"
|
||||
@row-edit-save="onRowEditSave"
|
||||
@row-edit-cancel="onRowEditCancel"
|
||||
@editing-meta-change="onEditingMetaChange"
|
||||
:unstyled="unstyled"
|
||||
:pt="pt"
|
||||
/>
|
||||
</template>
|
||||
</tr>
|
||||
<tr v-if="templates['expansion'] && expandedRows && d_rowExpanded" :id="expandedRowId + '_' + rowIndex + '_expansion'" :class="cx('rowExpansion')" role="row" v-bind="ptm('rowExpansion')">
|
||||
<td :colspan="columnsLength" v-bind="{ ...getColumnPT('bodycell'), ...ptm('rowExpansionCell') }">
|
||||
<component :is="templates['expansion']" :data="rowData" :index="rowIndex" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="templates['groupfooter'] && rowGroupMode === 'subheader' && shouldRenderRowGroupFooter" :class="cx('rowGroupFooter')" role="row" v-bind="ptm('rowGroupFooter')">
|
||||
<td :colspan="columnsLength - 1" v-bind="{ ...getColumnPT('bodycell'), ...ptm('rowGroupFooterCell') }">
|
||||
<component :is="templates['groupfooter']" :data="rowData" :index="rowIndex" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-else :class="cx('emptyMessage')" role="row" v-bind="ptm('emptyMessage')">
|
||||
<td :colspan="columnsLength" v-bind="{ ...getColumnPT('bodycell'), ...ptm('emptyMessageCell') }">
|
||||
<component v-if="templates.empty" :is="templates.empty" />
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseComponent from 'primevue/basecomponent';
|
||||
import ChevronDownIcon from 'primevue/icons/chevrondown';
|
||||
import ChevronRightIcon from 'primevue/icons/chevronright';
|
||||
import { ObjectUtils } from 'primevue/utils';
|
||||
import { mergeProps } from 'vue';
|
||||
import BodyCell from './BodyCell.vue';
|
||||
|
||||
export default {
|
||||
name: 'BodyRow',
|
||||
hostName: 'DataTable',
|
||||
extends: BaseComponent,
|
||||
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: {
|
||||
rowData: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
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, Function],
|
||||
default: null
|
||||
},
|
||||
expandedRowIcon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
collapsedRowIcon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
expandedRows: {
|
||||
type: [Array, Object],
|
||||
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
|
||||
},
|
||||
rowGroupHeaderStyle: {
|
||||
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
|
||||
},
|
||||
expandedRowId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
nameAttributeSelector: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
d_rowExpanded: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
expandedRows(newValue) {
|
||||
this.d_rowExpanded = this.dataKey ? newValue?.[ObjectUtils.resolveFieldData(this.rowData, this.dataKey)] !== undefined : newValue?.some((d) => this.equals(this.rowData, d));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
columnProp(col, prop) {
|
||||
return ObjectUtils.getVNodeProp(col, prop);
|
||||
},
|
||||
//@todo - update this method
|
||||
getColumnPT(key) {
|
||||
const columnMetaData = {
|
||||
parent: {
|
||||
instance: this,
|
||||
props: this.$props,
|
||||
state: this.$data
|
||||
}
|
||||
};
|
||||
|
||||
return mergeProps(this.ptm(`column.${key}`, { column: columnMetaData }), this.ptm(`column.${key}`, columnMetaData), this.ptmo(this.columnProp({}, 'pt'), key, columnMetaData));
|
||||
},
|
||||
//@todo - update this method
|
||||
getBodyRowPTOptions(key) {
|
||||
const datatable = this.$parentInstance?.$parentInstance?.$parentInstance;
|
||||
|
||||
return this.ptm(key, {
|
||||
context: {
|
||||
index: this.rowIndex,
|
||||
selectable: datatable?.rowHover || datatable?.selectionMode,
|
||||
selected: this.isSelected,
|
||||
stripedRows: datatable?.stripedRows || false
|
||||
}
|
||||
});
|
||||
},
|
||||
shouldRenderBodyCell(column) {
|
||||
if (this.rowGroupMode) {
|
||||
const field = this.columnProp(column, 'field');
|
||||
|
||||
if (this.rowGroupMode === 'subheader') {
|
||||
return this.groupRowsBy !== field;
|
||||
} else if (this.rowGroupMode === 'rowspan') {
|
||||
if (this.isGrouped(column)) {
|
||||
let prevRowData = this.value[this.rowIndex - 1];
|
||||
|
||||
if (prevRowData) {
|
||||
const currentRowFieldData = ObjectUtils.resolveFieldData(this.value[this.rowIndex], field);
|
||||
const previousRowFieldData = ObjectUtils.resolveFieldData(prevRowData, field);
|
||||
|
||||
return currentRowFieldData !== previousRowFieldData;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return !this.columnProp(column, 'hidden');
|
||||
}
|
||||
},
|
||||
calculateRowGroupSize(column) {
|
||||
if (this.isGrouped(column)) {
|
||||
let index = this.rowIndex;
|
||||
const field = this.columnProp(column, 'field');
|
||||
const currentRowFieldData = ObjectUtils.resolveFieldData(this.value[index], field);
|
||||
let nextRowFieldData = currentRowFieldData;
|
||||
let groupRowSpan = 0;
|
||||
|
||||
while (currentRowFieldData === nextRowFieldData) {
|
||||
groupRowSpan++;
|
||||
let nextRowData = this.value[++index];
|
||||
|
||||
if (nextRowData) {
|
||||
nextRowFieldData = ObjectUtils.resolveFieldData(nextRowData, field);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return groupRowSpan === 1 ? null : groupRowSpan;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
isGrouped(column) {
|
||||
const field = this.columnProp(column, 'field');
|
||||
|
||||
if (this.groupRowsBy && field) {
|
||||
if (Array.isArray(this.groupRowsBy)) return this.groupRowsBy.indexOf(field) > -1;
|
||||
else return this.groupRowsBy === field;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
findIndexInSelection(data) {
|
||||
return this.findIndex(data, this.selection);
|
||||
},
|
||||
findIndex(data, collection) {
|
||||
let index = -1;
|
||||
|
||||
if (collection && collection.length) {
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
if (this.equals(data, collection[i])) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
},
|
||||
equals(data1, data2) {
|
||||
return this.compareSelectionBy === 'equals' ? data1 === data2 : ObjectUtils.equals(data1, data2, this.dataKey);
|
||||
},
|
||||
onRowGroupToggle(event) {
|
||||
this.$emit('rowgroup-toggle', { originalEvent: event, data: this.rowData });
|
||||
},
|
||||
onRowClick(event) {
|
||||
this.$emit('row-click', { originalEvent: event, data: this.rowData, index: this.rowIndex });
|
||||
},
|
||||
onRowDblClick(event) {
|
||||
this.$emit('row-dblclick', { originalEvent: event, data: this.rowData, index: this.rowIndex });
|
||||
},
|
||||
onRowRightClick(event) {
|
||||
this.$emit('row-rightclick', { originalEvent: event, data: this.rowData, index: this.rowIndex });
|
||||
},
|
||||
onRowTouchEnd(event) {
|
||||
this.$emit('row-touchend', event);
|
||||
},
|
||||
onRowKeyDown(event) {
|
||||
this.$emit('row-keydown', { originalEvent: event, data: this.rowData, index: this.rowIndex });
|
||||
},
|
||||
onRowMouseDown(event) {
|
||||
this.$emit('row-mousedown', event);
|
||||
},
|
||||
onRowDragStart(event) {
|
||||
this.$emit('row-dragstart', { originalEvent: event, index: this.rowIndex });
|
||||
},
|
||||
onRowDragOver(event) {
|
||||
this.$emit('row-dragover', { originalEvent: event, index: this.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.d_rowExpanded = !this.d_rowExpanded;
|
||||
|
||||
this.$emit('row-toggle', { ...event, expanded: this.d_rowExpanded });
|
||||
},
|
||||
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);
|
||||
},
|
||||
getVirtualScrollerProp(option, options) {
|
||||
options = options || this.virtualScrollerContentProps;
|
||||
|
||||
return options ? options[option] : null;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rowIndex() {
|
||||
const getItemOptions = this.getVirtualScrollerProp('getItemOptions');
|
||||
|
||||
return getItemOptions ? getItemOptions(this.index).index : this.index;
|
||||
},
|
||||
rowStyles() {
|
||||
return this.rowStyle?.(this.rowData);
|
||||
},
|
||||
rowClasses() {
|
||||
let rowStyleClass = [];
|
||||
|
||||
if (this.rowClass) {
|
||||
let rowClassValue = this.rowClass(this.rowData);
|
||||
|
||||
if (rowClassValue) {
|
||||
rowStyleClass.push(rowClassValue);
|
||||
}
|
||||
}
|
||||
|
||||
return [this.cx('row', { rowData: this.rowData }), rowStyleClass];
|
||||
},
|
||||
rowTabindex() {
|
||||
if (this.selection === null && (this.selectionMode === 'single' || this.selectionMode === 'multiple')) {
|
||||
return this.rowIndex === 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
isRowEditing() {
|
||||
if (this.rowData && this.editingRows) {
|
||||
if (this.dataKey) return this.editingRowKeys ? this.editingRowKeys[ObjectUtils.resolveFieldData(this.rowData, this.dataKey)] !== undefined : false;
|
||||
else return this.findIndex(this.rowData, this.editingRows) > -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
isRowGroupExpanded() {
|
||||
if (this.expandableRowGroups && this.expandedRowGroups) {
|
||||
const groupFieldValue = ObjectUtils.resolveFieldData(this.rowData, this.groupRowsBy);
|
||||
|
||||
return this.expandedRowGroups.indexOf(groupFieldValue) > -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
isSelected() {
|
||||
if (this.rowData && this.selection) {
|
||||
if (this.dataKey) {
|
||||
return this.selectionKeys ? this.selectionKeys[ObjectUtils.resolveFieldData(this.rowData, this.dataKey)] !== undefined : false;
|
||||
} else {
|
||||
if (this.selection instanceof Array) return this.findIndexInSelection(this.rowData) > -1;
|
||||
else return this.equals(this.rowData, this.selection);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
isSelectedWithContextMenu() {
|
||||
if (this.rowData && this.contextMenuSelection) {
|
||||
return this.equals(this.rowData, this.contextMenuSelection, this.dataKey);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
shouldRenderRowGroupHeader() {
|
||||
const currentRowFieldData = ObjectUtils.resolveFieldData(this.rowData, this.groupRowsBy);
|
||||
const prevRowData = this.value[this.rowIndex - 1];
|
||||
|
||||
if (prevRowData) {
|
||||
const previousRowFieldData = ObjectUtils.resolveFieldData(prevRowData, this.groupRowsBy);
|
||||
|
||||
return currentRowFieldData !== previousRowFieldData;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
shouldRenderRowGroupFooter() {
|
||||
if (this.expandableRowGroups && !this.isRowGroupExpanded) {
|
||||
return false;
|
||||
} else {
|
||||
let currentRowFieldData = ObjectUtils.resolveFieldData(this.rowData, this.groupRowsBy);
|
||||
let nextRowData = this.value[this.rowIndex + 1];
|
||||
|
||||
if (nextRowData) {
|
||||
let nextRowFieldData = ObjectUtils.resolveFieldData(nextRowData, this.groupRowsBy);
|
||||
|
||||
return currentRowFieldData !== nextRowFieldData;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DTBodyCell: BodyCell,
|
||||
ChevronDownIcon: ChevronDownIcon,
|
||||
ChevronRightIcon: ChevronRightIcon
|
||||
}
|
||||
};
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue