pull/4987/head
Onur Senture 2023-12-19 18:03:58 +03:00
commit 2fe6b0cffc
19 changed files with 840 additions and 542 deletions

View File

@ -5,6 +5,12 @@ const TreeTableProps = [
default: 'null', default: 'null',
description: 'An array of treenodes.' description: 'An array of treenodes.'
}, },
{
name: 'dataKey',
type: 'string|function',
default: 'null',
description: 'Name of the field that uniquely identifies the a record in the data.'
},
{ {
name: 'expandedKeys', name: 'expandedKeys',
type: 'array', type: 'array',

View File

@ -202,7 +202,7 @@ export default {
return this.unstyled !== undefined ? this.unstyled : this.$config?.unstyled; return this.unstyled !== undefined ? this.unstyled : this.$config?.unstyled;
}, },
$params() { $params() {
const parentInstance = this.$parentInstance || this.$parent; const parentInstance = this._getHostInstance(this) || this.$parent;
return { return {
instance: this, instance: this,

View File

@ -167,7 +167,7 @@ export default {
default: false default: false
}, },
expandedRows: { expandedRows: {
type: Array, type: [Array, Object],
default: null default: null
}, },
expandedRowIcon: { expandedRowIcon: {

View 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>

View File

@ -124,7 +124,6 @@
:expandedRowIcon="expandedRowIcon" :expandedRowIcon="expandedRowIcon"
:collapsedRowIcon="collapsedRowIcon" :collapsedRowIcon="collapsedRowIcon"
:expandedRows="expandedRows" :expandedRows="expandedRows"
:expandedRowKeys="d_expandedRowKeys"
:expandedRowGroups="expandedRowGroups" :expandedRowGroups="expandedRowGroups"
:editingRows="editingRows" :editingRows="editingRows"
:editingRowKeys="d_editingRowKeys" :editingRowKeys="d_editingRowKeys"
@ -181,7 +180,6 @@
:expandedRowIcon="expandedRowIcon" :expandedRowIcon="expandedRowIcon"
:collapsedRowIcon="collapsedRowIcon" :collapsedRowIcon="collapsedRowIcon"
:expandedRows="expandedRows" :expandedRows="expandedRows"
:expandedRowKeys="d_expandedRowKeys"
:expandedRowGroups="expandedRowGroups" :expandedRowGroups="expandedRowGroups"
:editingRows="editingRows" :editingRows="editingRows"
:editingRowKeys="d_editingRowKeys" :editingRowKeys="d_editingRowKeys"
@ -345,7 +343,6 @@ export default {
d_multiSortMeta: this.multiSortMeta ? [...this.multiSortMeta] : [], d_multiSortMeta: this.multiSortMeta ? [...this.multiSortMeta] : [],
d_groupRowsSortMeta: null, d_groupRowsSortMeta: null,
d_selectionKeys: null, d_selectionKeys: null,
d_expandedRowKeys: null,
d_columnOrder: null, d_columnOrder: null,
d_editingRowKeys: null, d_editingRowKeys: null,
d_editingMeta: {}, d_editingMeta: {},
@ -363,6 +360,7 @@ export default {
colReorderIconWidth: null, colReorderIconWidth: null,
colReorderIconHeight: null, colReorderIconHeight: null,
draggedColumn: null, draggedColumn: null,
draggedColumnElement: null,
draggedRowIndex: null, draggedRowIndex: null,
droppedRowIndex: null, droppedRowIndex: null,
rowDragging: null, rowDragging: null,
@ -396,11 +394,6 @@ export default {
} }
} }
}, },
expandedRows(newValue) {
if (this.dataKey) {
this.updateExpandedRowKeys(newValue);
}
},
editingRows: { editingRows: {
immediate: true, immediate: true,
handler(newValue) { handler(newValue) {
@ -465,7 +458,9 @@ export default {
this.$emit('update:first', this.d_first); this.$emit('update:first', this.d_first);
this.$emit('update:rows', this.d_rows); this.$emit('update:rows', this.d_rows);
this.$emit('page', pageEvent); this.$emit('page', pageEvent);
this.$emit('value-change', this.processedData); this.$nextTick(() => {
this.$emit('value-change', this.processedData);
});
}, },
onColumnHeaderClick(e) { onColumnHeaderClick(e) {
const event = e.originalEvent; const event = e.originalEvent;
@ -514,7 +509,9 @@ export default {
} }
this.$emit('sort', this.createLazyLoadEvent(event)); this.$emit('sort', this.createLazyLoadEvent(event));
this.$emit('value-change', this.processedData); this.$nextTick(() => {
this.$emit('value-change', this.processedData);
});
} }
} }
}, },
@ -690,7 +687,9 @@ export default {
filterEvent.filteredValue = filteredValue; filterEvent.filteredValue = filteredValue;
this.$emit('filter', filterEvent); this.$emit('filter', filterEvent);
this.$emit('value-change', filteredValue); this.$nextTick(() => {
this.$emit('value-change', this.processedData);
});
return filteredValue; return filteredValue;
}, },
@ -1087,17 +1086,6 @@ export default {
this.d_selectionKeys[String(ObjectUtils.resolveFieldData(selection, this.dataKey))] = 1; this.d_selectionKeys[String(ObjectUtils.resolveFieldData(selection, this.dataKey))] = 1;
} }
}, },
updateExpandedRowKeys(expandedRows) {
if (expandedRows && expandedRows.length) {
this.d_expandedRowKeys = {};
for (let data of expandedRows) {
this.d_expandedRowKeys[String(ObjectUtils.resolveFieldData(data, this.dataKey))] = 1;
}
} else {
this.d_expandedRowKeys = null;
}
},
updateEditingRowKeys(editingRows) { updateEditingRowKeys(editingRows) {
if (editingRows && editingRows.length) { if (editingRows && editingRows.length) {
this.d_editingRowKeys = {}; this.d_editingRowKeys = {};
@ -1356,7 +1344,9 @@ export default {
else event.currentTarget.draggable = true; else event.currentTarget.draggable = true;
} }
}, },
onColumnHeaderDragStart(event) { onColumnHeaderDragStart(e) {
const { originalEvent: event, column } = e;
if (this.columnResizing) { if (this.columnResizing) {
event.preventDefault(); event.preventDefault();
@ -1366,18 +1356,20 @@ export default {
this.colReorderIconWidth = DomHandler.getHiddenElementOuterWidth(this.$refs.reorderIndicatorUp); this.colReorderIconWidth = DomHandler.getHiddenElementOuterWidth(this.$refs.reorderIndicatorUp);
this.colReorderIconHeight = DomHandler.getHiddenElementOuterHeight(this.$refs.reorderIndicatorUp); this.colReorderIconHeight = DomHandler.getHiddenElementOuterHeight(this.$refs.reorderIndicatorUp);
this.draggedColumn = this.findParentHeader(event.target); this.draggedColumn = column;
this.draggedColumnElement = this.findParentHeader(event.target);
event.dataTransfer.setData('text', 'b'); // Firefox requires this to make dragging possible event.dataTransfer.setData('text', 'b'); // Firefox requires this to make dragging possible
}, },
onColumnHeaderDragOver(event) { onColumnHeaderDragOver(e) {
const { originalEvent: event, column } = e;
let dropHeader = this.findParentHeader(event.target); let dropHeader = this.findParentHeader(event.target);
if (this.reorderableColumns && this.draggedColumn && dropHeader) { if (this.reorderableColumns && this.draggedColumnElement && dropHeader && !this.columnProp(column, 'frozen')) {
event.preventDefault(); event.preventDefault();
let containerOffset = DomHandler.getOffset(this.$el); let containerOffset = DomHandler.getOffset(this.$el);
let dropHeaderOffset = DomHandler.getOffset(dropHeader); let dropHeaderOffset = DomHandler.getOffset(dropHeader);
if (this.draggedColumn !== dropHeader) { if (this.draggedColumnElement !== dropHeader) {
let targetLeft = dropHeaderOffset.left - containerOffset.left; let targetLeft = dropHeaderOffset.left - containerOffset.left;
let columnCenter = dropHeaderOffset.left + dropHeader.offsetWidth / 2; let columnCenter = dropHeaderOffset.left + dropHeader.offsetWidth / 2;
@ -1399,18 +1391,22 @@ export default {
} }
} }
}, },
onColumnHeaderDragLeave(event) { onColumnHeaderDragLeave(e) {
if (this.reorderableColumns && this.draggedColumn) { const { originalEvent: event } = e;
if (this.reorderableColumns && this.draggedColumnElement) {
event.preventDefault(); event.preventDefault();
this.$refs.reorderIndicatorUp.style.display = 'none'; this.$refs.reorderIndicatorUp.style.display = 'none';
this.$refs.reorderIndicatorDown.style.display = 'none'; this.$refs.reorderIndicatorDown.style.display = 'none';
} }
}, },
onColumnHeaderDrop(event) { onColumnHeaderDrop(e) {
const { originalEvent: event, column } = e;
event.preventDefault(); event.preventDefault();
if (this.draggedColumn) { if (this.draggedColumnElement) {
let dragIndex = DomHandler.index(this.draggedColumn); let dragIndex = DomHandler.index(this.draggedColumnElement);
let dropIndex = DomHandler.index(this.findParentHeader(event.target)); let dropIndex = DomHandler.index(this.findParentHeader(event.target));
let allowDrop = dragIndex !== dropIndex; let allowDrop = dragIndex !== dropIndex;
@ -1419,19 +1415,42 @@ export default {
} }
if (allowDrop) { if (allowDrop) {
ObjectUtils.reorderArray(this.columns, dragIndex, dropIndex); let isSameColumn = (col1, col2) =>
this.columnProp(col1, 'columnKey') || this.columnProp(col2, 'columnKey') ? this.columnProp(col1, 'columnKey') === this.columnProp(col2, 'columnKey') : this.columnProp(col1, 'field') === this.columnProp(col2, 'field');
let dragColIndex = this.columns.findIndex((child) => isSameColumn(child, this.draggedColumn));
let dropColIndex = this.columns.findIndex((child) => isSameColumn(child, column));
let widths = [];
let headers = DomHandler.find(this.$el, 'thead[data-pc-section="thead"] > tr > th');
headers.forEach((header) => widths.push(DomHandler.getOuterWidth(header)));
const movedItem = widths.find((_, index) => index === dragColIndex);
const remainingItems = widths.filter((_, index) => index !== dragColIndex);
const reorderedWidths = [...remainingItems.slice(0, dropColIndex), movedItem, ...remainingItems.slice(dropColIndex)];
this.addColumnWidthStyles(reorderedWidths);
if (dropColIndex < dragColIndex && this.dropPosition === 1) {
dropColIndex++;
}
if (dropColIndex > dragColIndex && this.dropPosition === -1) {
dropColIndex--;
}
ObjectUtils.reorderArray(this.columns, dragColIndex, dropColIndex);
this.updateReorderableColumns(); this.updateReorderableColumns();
this.$emit('column-reorder', { this.$emit('column-reorder', {
originalEvent: event, originalEvent: event,
dragIndex: dragIndex, dragIndex: dragColIndex,
dropIndex: dropIndex dropIndex: dropColIndex
}); });
} }
this.$refs.reorderIndicatorUp.style.display = 'none'; this.$refs.reorderIndicatorUp.style.display = 'none';
this.$refs.reorderIndicatorDown.style.display = 'none'; this.$refs.reorderIndicatorDown.style.display = 'none';
this.draggedColumn.draggable = false; this.draggedColumnElement.draggable = false;
this.draggedColumnElement = null;
this.draggedColumn = null; this.draggedColumn = null;
this.dropPosition = null; this.dropPosition = null;
} }
@ -1557,31 +1576,22 @@ export default {
event.preventDefault(); event.preventDefault();
}, },
toggleRow(event) { toggleRow(event) {
let rowData = event.data; const { expanded, ...rest } = event;
let expanded; const rowData = event.data;
let expandedRowIndex; let expandedRows;
let _expandedRows = this.expandedRows ? [...this.expandedRows] : [];
if (this.dataKey) { if (this.dataKey) {
expanded = this.d_expandedRowKeys ? this.d_expandedRowKeys[ObjectUtils.resolveFieldData(rowData, this.dataKey)] !== undefined : false; const value = ObjectUtils.resolveFieldData(rowData, this.dataKey);
expandedRows = this.expandedRows ? { ...this.expandedRows } : {};
expanded ? (expandedRows[value] = true) : delete expandedRows[value];
} else { } else {
expandedRowIndex = this.findIndex(rowData, this.expandedRows); expandedRows = this.expandedRows ? [...this.expandedRows] : [];
expanded = expandedRowIndex > -1; expanded ? expandedRows.push(rowData) : (expandedRows = expandedRows.filter((d) => !this.equals(rowData, d)));
} }
if (expanded) { this.$emit('update:expandedRows', expandedRows);
if (expandedRowIndex == null) { expanded ? this.$emit('row-expand', rest) : this.$emit('row-collapse', rest);
expandedRowIndex = this.findIndex(rowData, this.expandedRows);
}
_expandedRows.splice(expandedRowIndex, 1);
this.$emit('update:expandedRows', _expandedRows);
this.$emit('row-collapse', event);
} else {
_expandedRows.push(rowData);
this.$emit('update:expandedRows', _expandedRows);
this.$emit('row-expand', event);
}
}, },
toggleRowGroup(e) { toggleRowGroup(e) {
const event = e.originalEvent; const event = e.originalEvent;
@ -1655,7 +1665,6 @@ export default {
if (this.expandedRows) { if (this.expandedRows) {
state.expandedRows = this.expandedRows; state.expandedRows = this.expandedRows;
state.expandedRowKeys = this.d_expandedRowKeys;
} }
if (this.expandedRowGroups) { if (this.expandedRowGroups) {
@ -1717,7 +1726,6 @@ export default {
} }
if (restoredState.expandedRows) { if (restoredState.expandedRows) {
this.d_expandedRowKeys = restoredState.expandedRowKeys;
this.$emit('update:expandedRows', restoredState.expandedRows); this.$emit('update:expandedRows', restoredState.expandedRows);
} }
@ -1744,6 +1752,26 @@ export default {
state.tableWidth = DomHandler.getOuterWidth(this.$refs.table) + 'px'; state.tableWidth = DomHandler.getOuterWidth(this.$refs.table) + 'px';
} }
}, },
addColumnWidthStyles(widths) {
this.createStyleElement();
let innerHTML = '';
let selector = `[data-pc-name="datatable"][${this.attributeSelector}] > [data-pc-section="wrapper"] ${this.virtualScrollerDisabled ? '' : '> [data-pc-name="virtualscroller"]'} > table[data-pc-section="table"]`;
widths.forEach((width, index) => {
let style = `width: ${width}px !important; max-width: ${width}px !important`;
innerHTML += `
${selector} > thead[data-pc-section="thead"] > tr > th:nth-child(${index + 1}),
${selector} > tbody[data-pc-section="tbody"] > tr > td:nth-child(${index + 1}),
${selector} > tfoot[data-pc-section="tfoot"] > tr > td:nth-child(${index + 1}) {
${style}
}
`;
});
this.styleElement.innerHTML = innerHTML;
},
restoreColumnWidths() { restoreColumnWidths() {
if (this.columnWidthsState) { if (this.columnWidthsState) {
let widths = this.columnWidthsState.split(','); let widths = this.columnWidthsState.split(',');
@ -1755,24 +1783,7 @@ export default {
} }
if (ObjectUtils.isNotEmpty(widths)) { if (ObjectUtils.isNotEmpty(widths)) {
this.createStyleElement(); this.addColumnWidthStyles(widths);
let innerHTML = '';
let selector = `[data-pc-name="datatable"][${this.attributeSelector}] > [data-pc-section="wrapper"] ${this.virtualScrollerDisabled ? '' : '> [data-pc-name="virtualscroller"]'} > table[data-pc-section="table"]`;
widths.forEach((width, index) => {
let style = `width: ${width}px !important; max-width: ${width}px !important`;
innerHTML += `
${selector} > thead[data-pc-section="thead"] > tr > th:nth-child(${index + 1}),
${selector} > tbody[data-pc-section="tbody"] > tr > td:nth-child(${index + 1}),
${selector} > tfoot[data-pc-section="tfoot"] > tr > td:nth-child(${index + 1}) {
${style}
}
`;
});
this.styleElement.innerHTML = innerHTML;
} }
} }
}, },

View File

@ -237,16 +237,16 @@ export default {
this.$emit('column-mousedown', { originalEvent: event, column: this.column }); this.$emit('column-mousedown', { originalEvent: event, column: this.column });
}, },
onDragStart(event) { onDragStart(event) {
this.$emit('column-dragstart', event); this.$emit('column-dragstart', { originalEvent: event, column: this.column });
}, },
onDragOver(event) { onDragOver(event) {
this.$emit('column-dragover', event); this.$emit('column-dragover', { originalEvent: event, column: this.column });
}, },
onDragLeave(event) { onDragLeave(event) {
this.$emit('column-dragleave', event); this.$emit('column-dragleave', { originalEvent: event, column: this.column });
}, },
onDrop(event) { onDrop(event) {
this.$emit('column-drop', event); this.$emit('column-drop', { originalEvent: event, column: this.column });
}, },
onResizeStart(event) { onResizeStart(event) {
this.$emit('column-resizestart', event); this.$emit('column-resizestart', event);

View File

@ -1,128 +1,78 @@
<template> <template>
<tbody :ref="bodyRef" :class="cx('tbody')" role="rowgroup" :style="bodyStyle" v-bind="ptm('tbody', ptmTBodyOptions)"> <tbody :ref="bodyRef" :class="cx('tbody')" role="rowgroup" :style="bodyStyle" v-bind="ptm('tbody', ptmTBodyOptions)">
<template v-if="!empty"> <template v-if="!empty">
<template v-for="(rowData, index) of value"> <template v-for="(rowData, rowIndex) of value" :key="rowIndex">
<tr <DTBodyRow
v-if="templates['groupheader'] && rowGroupMode === 'subheader' && shouldRenderRowGroupHeader(value, rowData, getRowIndex(index))" :rowData="rowData"
:key="getRowKey(rowData, getRowIndex(index)) + '_subheader'" :index="rowIndex"
:class="cx('rowGroupHeader')" :value="value"
:style="rowGroupHeaderStyle" :columns="columns"
role="row" :frozenRow="frozenRow"
v-bind="ptm('rowGroupHeader')" :empty="empty"
> :first="first"
<td :colspan="columnsLength - 1" v-bind="{ ...getColumnPT('bodycell'), ...ptm('rowGroupHeaderCell') }"> :dataKey="dataKey"
<button v-if="expandableRowGroups" :class="cx('rowGroupToggler')" @click="onRowGroupToggle($event, rowData)" type="button" v-bind="ptm('rowGroupToggler')"> :selection="selection"
<component v-if="templates['rowgrouptogglericon']" :is="templates['rowgrouptogglericon']" :expanded="isRowGroupExpanded(rowData)" /> :selectionKeys="selectionKeys"
<template v-else> :selectionMode="selectionMode"
<span v-if="isRowGroupExpanded(rowData) && expandedRowIcon" :class="[cx('rowGroupTogglerIcon'), expandedRowIcon]" v-bind="ptm('rowGroupTogglerIcon')" /> :contextMenu="contextMenu"
<ChevronDownIcon v-else-if="isRowGroupExpanded(rowData) && !expandedRowIcon" :class="cx('rowGroupTogglerIcon')" v-bind="ptm('rowGroupTogglerIcon')" /> :contextMenuSelection="contextMenuSelection"
<span v-else-if="!isRowGroupExpanded(rowData) && collapsedRowIcon" :class="[cx('rowGroupTogglerIcon'), collapsedRowIcon]" v-bind="ptm('rowGroupTogglerIcon')" /> :rowGroupMode="rowGroupMode"
<ChevronRightIcon v-else-if="!isRowGroupExpanded(rowData) && !collapsedRowIcon" :class="cx('rowGroupTogglerIcon')" v-bind="ptm('rowGroupTogglerIcon')" /> :groupRowsBy="groupRowsBy"
</template> :expandableRowGroups="expandableRowGroups"
</button> :rowClass="rowClass"
<component :is="templates['groupheader']" :data="rowData" :index="getRowIndex(index)" /> :rowStyle="rowStyle"
</td> :editMode="editMode"
</tr> :compareSelectionBy="compareSelectionBy"
<tr :scrollable="scrollable"
v-if="expandableRowGroups ? isRowGroupExpanded(rowData) : true" :expandedRowIcon="expandedRowIcon"
:key="getRowKey(rowData, getRowIndex(index))" :collapsedRowIcon="collapsedRowIcon"
:class="getRowClass(rowData)" :expandedRows="expandedRows"
:style="getRowStyle(rowData)" :expandedRowGroups="expandedRowGroups"
:tabindex="setRowTabindex(index)" :editingRows="editingRows"
role="row" :editingRowKeys="editingRowKeys"
:aria-selected="selectionMode ? isSelected(rowData) : null" :templates="templates"
@click="onRowClick($event, rowData, getRowIndex(index))" :responsiveLayout="responsiveLayout"
@dblclick="onRowDblClick($event, rowData, getRowIndex(index))" :virtualScrollerContentProps="virtualScrollerContentProps"
@contextmenu="onRowRightClick($event, rowData, getRowIndex(index))" :isVirtualScrollerDisabled="isVirtualScrollerDisabled"
@touchend="onRowTouchEnd($event)" :editingMeta="editingMeta"
@keydown.self="onRowKeyDown($event, rowData, getRowIndex(index))" :rowGroupHeaderStyle="rowGroupHeaderStyle"
@mousedown="onRowMouseDown($event)" :expandedRowId="expandedRowId"
@dragstart="onRowDragStart($event, getRowIndex(index))" :nameAttributeSelector="nameAttributeSelector"
@dragover="onRowDragOver($event, getRowIndex(index))" @rowgroup-toggle="$emit('rowgroup-toggle', $event)"
@dragleave="onRowDragLeave($event)" @row-click="$emit('row-click', $event)"
@dragend="onRowDragEnd($event)" @row-dblclick="$emit('row-dblclick', $event)"
@drop="onRowDrop($event)" @row-rightclick="$emit('row-rightclick', $event)"
v-bind="getBodyRowPTOptions('bodyRow', rowData, index)" @row-touchend="$emit('row-touchend', $event)"
:data-p-selectable-row="selectionMode ? true : false" @row-keydown="$emit('row-keydown', $event)"
:data-p-highlight="selection && isSelected(rowData)" @row-mousedown="$emit('row-mousedown', $event)"
:data-p-highlight-contextmenu="contextMenuSelection && isSelectedWithContextMenu(rowData)" @row-dragstart="$emit('row-dragstart', $event)"
> @row-dragover="$emit('row-dragover', $event)"
<template v-for="(col, i) of columns"> @row-dragleave="$emit('row-dragleave', $event)"
<DTBodyCell @row-dragend="$emit('row-dragend', $event)"
v-if="shouldRenderBodyCell(value, col, getRowIndex(index))" @row-drop="$emit('row-drop', $event)"
:key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i" @row-toggle="$emit('row-toggle', $event)"
:rowData="rowData" @radio-change="$emit('radio-change', $event)"
:column="col" @checkbox-change="$emit('checkbox-change', $event)"
:rowIndex="getRowIndex(index)" @cell-edit-init="$emit('cell-edit-init', $event)"
:index="i" @cell-edit-complete="$emit('cell-edit-complete', $event)"
:selected="isSelected(rowData)" @cell-edit-cancel="$emit('cell-edit-cancel', $event)"
:frozenRow="frozenRow" @row-edit-init="$emit('row-edit-init', $event)"
:rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(value, col, getRowIndex(index)) : null" @row-edit-save="$emit('row-edit-save', $event)"
:editMode="editMode" @row-edit-cancel="$emit('row-edit-cancel', $event)"
:editing="editMode === 'row' && isRowEditing(rowData)" @editing-meta-change="$emit('editing-meta-change', $event)"
:editingMeta="editingMeta" :unstyled="unstyled"
:responsiveLayout="responsiveLayout" :pt="pt"
: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"
:unstyled="unstyled"
:pt="pt"
/>
</template>
</tr>
<tr
v-if="templates['expansion'] && expandedRows && isRowExpanded(rowData)"
:key="getRowKey(rowData, getRowIndex(index)) + '_expansion'"
:id="expandedRowId + '_' + index + '_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="getRowIndex(index)" />
</td>
</tr>
<tr
v-if="templates['groupfooter'] && rowGroupMode === 'subheader' && shouldRenderRowGroupFooter(value, rowData, getRowIndex(index))"
:key="getRowKey(rowData, getRowIndex(index)) + '_subfooter'"
: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="getRowIndex(index)" />
</td>
</tr>
</template> </template>
</template> </template>
<tr v-else :class="cx('emptyMessage')" role="row" v-bind="ptm('emptyMessage')"> <DTBodyRow v-else :empty="empty" :columns="columns" :templates="templates" />
<td :colspan="columnsLength" v-bind="{ ...getColumnPT('bodycell'), ...ptm('emptyMessageCell') }">
<component v-if="templates.empty" :is="templates.empty" />
</td>
</tr>
</tbody> </tbody>
</template> </template>
<script> <script>
import BaseComponent from 'primevue/basecomponent'; import BaseComponent from 'primevue/basecomponent';
import ChevronDownIcon from 'primevue/icons/chevrondown'; import { DomHandler, UniqueComponentId } from 'primevue/utils';
import ChevronRightIcon from 'primevue/icons/chevronright'; import BodyRow from './BodyRow.vue';
import { DomHandler, ObjectUtils, UniqueComponentId } from 'primevue/utils';
import { mergeProps } from 'vue';
import BodyCell from './BodyCell.vue';
export default { export default {
name: 'TableBody', name: 'TableBody',
@ -202,11 +152,7 @@ export default {
default: null default: null
}, },
expandedRows: { expandedRows: {
type: Array, type: [Array, Object],
default: null
},
expandedRowKeys: {
type: null,
default: null default: null
}, },
selection: { selection: {
@ -280,9 +226,7 @@ export default {
}, },
data() { data() {
return { return {
rowGroupHeaderStyleObject: {}, rowGroupHeaderStyleObject: {}
tabindexArray: [],
isARowSelected: false
}; };
}, },
mounted() { mounted() {
@ -304,271 +248,6 @@ export default {
} }
}, },
methods: { methods: {
columnProp(col, prop) {
return ObjectUtils.getVNodeProp(col, prop);
},
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.getColumnProp({}), key, columnMetaData));
},
getColumnProp(column) {
return column.props && column.props.pt ? column.props.pt : undefined; //@todo
},
getBodyRowPTOptions(key, rowdata, index) {
return this.ptm(key, {
context: {
index,
selectable: this.$parentInstance?.$parentInstance?.rowHover || this.$parentInstance?.$parentInstance?.selectionMode,
selected: this.isSelected(rowdata),
stripedRows: this.$parentInstance?.$parentInstance?.stripedRows || false
}
});
},
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 : index;
},
getRowStyle(rowData) {
if (this.rowStyle) {
return this.rowStyle(rowData);
}
},
getRowClass(rowData) {
let rowStyleClass = [];
if (this.rowClass) {
let rowClassValue = this.rowClass(rowData);
if (rowClassValue) {
rowStyleClass.push(rowClassValue);
}
}
return [this.cx('row', { rowData }), 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() { updateFrozenRowStickyPosition() {
this.$el.style.top = DomHandler.getOuterHeight(this.$el.previousElementSibling) + 'px'; this.$el.style.top = DomHandler.getOuterHeight(this.$el.previousElementSibling) + 'px';
}, },
@ -587,26 +266,9 @@ export default {
const contentRef = this.getVirtualScrollerProp('contentRef'); const contentRef = this.getVirtualScrollerProp('contentRef');
contentRef && contentRef(el); contentRef && contentRef(el);
},
setRowTabindex(index) {
if (this.selection === null && (this.selectionMode === 'single' || this.selectionMode === 'multiple')) {
return index === 0 ? 0 : -1;
}
return -1;
} }
}, },
computed: { 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() { rowGroupHeaderStyle() {
if (this.scrollable) { if (this.scrollable) {
return { top: this.rowGroupHeaderStyleObject.top }; return { top: this.rowGroupHeaderStyleObject.top };
@ -617,24 +279,22 @@ export default {
bodyStyle() { bodyStyle() {
return this.getVirtualScrollerProp('contentStyle'); return this.getVirtualScrollerProp('contentStyle');
}, },
expandedRowId() {
return UniqueComponentId();
},
nameAttributeSelector() {
return UniqueComponentId();
},
ptmTBodyOptions() { ptmTBodyOptions() {
return { return {
context: { context: {
scrollable: this.$parentInstance?.$parentInstance?.scrollable scrollable: this.$parentInstance?.$parentInstance?.scrollable
} }
}; };
},
expandedRowId() {
return UniqueComponentId();
},
nameAttributeSelector() {
return UniqueComponentId();
} }
}, },
components: { components: {
DTBodyCell: BodyCell, DTBodyRow: BodyRow
ChevronDownIcon: ChevronDownIcon,
ChevronRightIcon: ChevronRightIcon
} }
}; };
</script> </script>

View File

@ -368,7 +368,7 @@ const classes = {
rowgroupHeader: 'p-rowgroup-header', rowgroupHeader: 'p-rowgroup-header',
rowGroupToggler: 'p-row-toggler p-link', rowGroupToggler: 'p-row-toggler p-link',
rowGroupTogglerIcon: 'p-row-toggler-icon', rowGroupTogglerIcon: 'p-row-toggler-icon',
row: ({ instance, props, rowData }) => { row: ({ instance, props }) => {
let rowStyleClass = []; let rowStyleClass = [];
if (props.selectionMode) { if (props.selectionMode) {
@ -377,13 +377,13 @@ const classes = {
if (props.selection) { if (props.selection) {
rowStyleClass.push({ rowStyleClass.push({
'p-highlight': instance.isSelected(rowData) 'p-highlight': instance.isSelected
}); });
} }
if (props.contextMenuSelection) { if (props.contextMenuSelection) {
rowStyleClass.push({ rowStyleClass.push({
'p-highlight-contextmenu': instance.isSelectedWithContextMenu(rowData) 'p-highlight-contextmenu': instance.isSelectedWithContextMenu
}); });
} }

View File

@ -15,7 +15,7 @@
@keydown="onKeyDown" @keydown="onKeyDown"
@mousedown="onMouseDown" @mousedown="onMouseDown"
@mouseup="onMouseUp" @mouseup="onMouseUp"
@touchstart="onTouchStart" @touchstart.passive="onTouchStart"
@touchend="onTouchEnd" @touchend="onTouchEnd"
v-bind="ptm('svg')" v-bind="ptm('svg')"
> >

View File

@ -10,6 +10,10 @@ export default {
type: null, type: null,
default: null default: null
}, },
dataKey: {
type: [String, Function],
default: 'key'
},
expandedKeys: { expandedKeys: {
type: null, type: null,
default: null default: null

View File

@ -395,6 +395,11 @@ export interface TreeTableProps {
* An array of treenodes. * An array of treenodes.
*/ */
value?: TreeNode[] | undefined; value?: TreeNode[] | undefined;
/**
* Name of the field that uniquely identifies the a record in the data.
* @defaultValue "key"
*/
dataKey?: string | ((item: any) => string) | undefined;
/** /**
* A map of keys to represent the state of the tree expansion state in controlled mode. * A map of keys to represent the state of the tree expansion state in controlled mode.
* @see TreeTableExpandedKeys * @see TreeTableExpandedKeys

View File

@ -83,7 +83,8 @@
<template v-if="!empty"> <template v-if="!empty">
<TTRow <TTRow
v-for="(node, index) of dataToRender" v-for="(node, index) of dataToRender"
:key="node.key" :key="nodeKey(node)"
:dataKey="dataKey"
:columns="columns" :columns="columns"
:node="node" :node="node"
:level="0" :level="0"
@ -252,8 +253,7 @@ export default {
}; };
}, },
onNodeToggle(node) { onNodeToggle(node) {
const key = node.key; const key = this.nodeKey(node);
if (this.d_expandedKeys[key]) { if (this.d_expandedKeys[key]) {
delete this.d_expandedKeys[key]; delete this.d_expandedKeys[key];
this.$emit('node-collapse', node); this.$emit('node-collapse', node);
@ -273,9 +273,13 @@ export default {
this.$emit('update:selectionKeys', _selectionKeys); this.$emit('update:selectionKeys', _selectionKeys);
} }
}, },
nodeKey(node) {
return ObjectUtils.resolveFieldData(node, this.dataKey);
},
handleSelectionWithMetaKey(event) { handleSelectionWithMetaKey(event) {
const originalEvent = event.originalEvent; const originalEvent = event.originalEvent;
const node = event.node; const node = event.node;
const nodeKey = this.nodeKey(node);
const metaKey = originalEvent.metaKey || originalEvent.ctrlKey; const metaKey = originalEvent.metaKey || originalEvent.ctrlKey;
const selected = this.isNodeSelected(node); const selected = this.isNodeSelected(node);
let _selectionKeys; let _selectionKeys;
@ -285,7 +289,7 @@ export default {
_selectionKeys = {}; _selectionKeys = {};
} else { } else {
_selectionKeys = { ...this.selectionKeys }; _selectionKeys = { ...this.selectionKeys };
delete _selectionKeys[node.key]; delete _selectionKeys[nodeKey];
} }
this.$emit('node-unselect', node); this.$emit('node-unselect', node);
@ -296,7 +300,7 @@ export default {
_selectionKeys = !metaKey ? {} : this.selectionKeys ? { ...this.selectionKeys } : {}; _selectionKeys = !metaKey ? {} : this.selectionKeys ? { ...this.selectionKeys } : {};
} }
_selectionKeys[node.key] = true; _selectionKeys[nodeKey] = true;
this.$emit('node-select', node); this.$emit('node-select', node);
} }
@ -304,6 +308,7 @@ export default {
}, },
handleSelectionWithoutMetaKey(event) { handleSelectionWithoutMetaKey(event) {
const node = event.node; const node = event.node;
const nodeKey = this.nodeKey(node);
const selected = this.isNodeSelected(node); const selected = this.isNodeSelected(node);
let _selectionKeys; let _selectionKeys;
@ -313,18 +318,18 @@ export default {
this.$emit('node-unselect', node); this.$emit('node-unselect', node);
} else { } else {
_selectionKeys = {}; _selectionKeys = {};
_selectionKeys[node.key] = true; _selectionKeys[nodeKey] = true;
this.$emit('node-select', node); this.$emit('node-select', node);
} }
} else { } else {
if (selected) { if (selected) {
_selectionKeys = { ...this.selectionKeys }; _selectionKeys = { ...this.selectionKeys };
delete _selectionKeys[node.key]; delete _selectionKeys[nodeKey];
this.$emit('node-unselect', node); this.$emit('node-unselect', node);
} else { } else {
_selectionKeys = this.selectionKeys ? { ...this.selectionKeys } : {}; _selectionKeys = this.selectionKeys ? { ...this.selectionKeys } : {};
_selectionKeys[node.key] = true; _selectionKeys[nodeKey] = true;
this.$emit('node-select', node); this.$emit('node-select', node);
} }
@ -572,7 +577,7 @@ export default {
return matched; return matched;
}, },
isNodeSelected(node) { isNodeSelected(node) {
return this.selectionMode && this.selectionKeys ? this.selectionKeys[node.key] === true : false; return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.nodeKey(node)] === true : false;
}, },
isNodeLeaf(node) { isNodeLeaf(node) {
return node.leaf === false ? false : !(node.children && node.children.length); return node.leaf === false ? false : !(node.children && node.children.length);

View File

@ -41,7 +41,8 @@
<template v-if="expanded && node.children && node.children.length"> <template v-if="expanded && node.children && node.children.length">
<TreeTableRow <TreeTableRow
v-for="childNode of node.children" v-for="childNode of node.children"
:key="childNode.key" :key="nodeKey(childNode)"
:dataKey="dataKey"
:columns="columns" :columns="columns"
:node="childNode" :node="childNode"
:parentNode="node" :parentNode="node"
@ -76,6 +77,10 @@ export default {
type: null, type: null,
default: null default: null
}, },
dataKey: {
type: [String, Function],
default: 'key'
},
parentNode: { parentNode: {
type: null, type: null,
default: null default: null
@ -150,6 +155,9 @@ export default {
onTouchEnd() { onTouchEnd() {
this.nodeTouched = true; this.nodeTouched = true;
}, },
nodeKey(node) {
return ObjectUtils.resolveFieldData(node, this.dataKey);
},
onKeyDown(event, item) { onKeyDown(event, item) {
switch (event.code) { switch (event.code) {
case 'ArrowDown': case 'ArrowDown':
@ -320,8 +328,8 @@ export default {
}); });
}, },
propagateDown(node, check, selectionKeys) { propagateDown(node, check, selectionKeys) {
if (check) selectionKeys[node.key] = { checked: true, partialChecked: false }; if (check) selectionKeys[this.nodeKey(node)] = { checked: true, partialChecked: false };
else delete selectionKeys[node.key]; else delete selectionKeys[this.nodeKey(node)];
if (node.children && node.children.length) { if (node.children && node.children.length) {
for (let child of node.children) { for (let child of node.children) {
@ -336,19 +344,19 @@ export default {
let childPartialSelected = false; let childPartialSelected = false;
for (let child of this.node.children) { for (let child of this.node.children) {
if (_selectionKeys[child.key] && _selectionKeys[child.key].checked) checkedChildCount++; if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].checked) checkedChildCount++;
else if (_selectionKeys[child.key] && _selectionKeys[child.key].partialChecked) childPartialSelected = true; else if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].partialChecked) childPartialSelected = true;
} }
if (check && checkedChildCount === this.node.children.length) { if (check && checkedChildCount === this.node.children.length) {
_selectionKeys[this.node.key] = { checked: true, partialChecked: false }; _selectionKeys[this.nodeKey(this.node)] = { checked: true, partialChecked: false };
} else { } else {
if (!check) { if (!check) {
delete _selectionKeys[this.node.key]; delete _selectionKeys[this.nodeKey(this.node)];
} }
if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.node.key] = { checked: false, partialChecked: true }; if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: true };
else _selectionKeys[this.node.key] = { checked: false, partialChecked: false }; else _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: false };
} }
this.$emit('checkbox-change', { this.$emit('checkbox-change', {
@ -364,19 +372,19 @@ export default {
let childPartialSelected = false; let childPartialSelected = false;
for (let child of this.node.children) { for (let child of this.node.children) {
if (_selectionKeys[child.key] && _selectionKeys[child.key].checked) checkedChildCount++; if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].checked) checkedChildCount++;
else if (_selectionKeys[child.key] && _selectionKeys[child.key].partialChecked) childPartialSelected = true; else if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].partialChecked) childPartialSelected = true;
} }
if (check && checkedChildCount === this.node.children.length) { if (check && checkedChildCount === this.node.children.length) {
_selectionKeys[this.node.key] = { checked: true, partialChecked: false }; _selectionKeys[this.nodeKey(this.node)] = { checked: true, partialChecked: false };
} else { } else {
if (!check) { if (!check) {
delete _selectionKeys[this.node.key]; delete _selectionKeys[this.nodeKey(this.node)];
} }
if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.node.key] = { checked: false, partialChecked: true }; if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: true };
else _selectionKeys[this.node.key] = { checked: false, partialChecked: false }; else _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: false };
} }
this.$emit('checkbox-change', { this.$emit('checkbox-change', {
@ -402,19 +410,19 @@ export default {
return [this.node.styleClass, this.cx('row')]; return [this.node.styleClass, this.cx('row')];
}, },
expanded() { expanded() {
return this.expandedKeys && this.expandedKeys[this.node.key] === true; return this.expandedKeys && this.expandedKeys[this.nodeKey(this.node)] === true;
}, },
leaf() { leaf() {
return this.node.leaf === false ? false : !(this.node.children && this.node.children.length); return this.node.leaf === false ? false : !(this.node.children && this.node.children.length);
}, },
selected() { selected() {
return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.node.key] === true : false; return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] === true : false;
}, },
checked() { checked() {
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].checked : false; return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].checked : false;
}, },
partialChecked() { partialChecked() {
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].partialChecked : false; return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].partialChecked : false;
}, },
getAriaSelected() { getAriaSelected() {
return this.selectionMode === 'single' || this.selectionMode === 'multiple' ? this.selected : null; return this.selectionMode === 'single' || this.selectionMode === 'multiple' ? this.selected : null;

View File

@ -114,7 +114,7 @@ export default {
index(element) { index(element) {
if (element) { if (element) {
let children = element.parentNode.childNodes; let children = this.getParentNode(element)?.childNodes;
let num = 0; let num = 0;
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
@ -392,8 +392,20 @@ export default {
} }
}, },
getParentNode(element) {
let parent = element?.parentNode;
if (parent && parent.host) {
parent = parent.host;
}
return parent;
},
getParents(element, parents = []) { getParents(element, parents = []) {
return element['parentNode'] === null ? parents : this.getParents(element.parentNode, parents.concat([element.parentNode])); const parent = this.getParentNode(element);
return parent === null ? parents : this.getParents(parent, parents.concat([parent]));
}, },
getScrollableParents(element) { getScrollableParents(element) {
@ -649,7 +661,7 @@ export default {
}, },
isExist(element) { isExist(element) {
return !!(element !== null && typeof element !== 'undefined' && element.nodeName && element.parentNode); return !!(element !== null && typeof element !== 'undefined' && element.nodeName && this.getParentNode(element));
}, },
isClient() { isClient() {

View File

@ -203,13 +203,15 @@ export default {
}, },
getVNodeProp(vnode, prop) { getVNodeProp(vnode, prop) {
let props = vnode.props; if (vnode) {
let props = vnode.props;
if (props) { if (props) {
let kebabProp = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); let kebabProp = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
let propName = Object.prototype.hasOwnProperty.call(props, kebabProp) ? kebabProp : prop; let propName = Object.prototype.hasOwnProperty.call(props, kebabProp) ? kebabProp : prop;
return vnode.type.extends.props[prop].type === Boolean && props[propName] === '' ? true : props[propName]; return vnode.type.extends.props[prop].type === Boolean && props[propName] === '' ? true : props[propName];
}
} }
return null; return null;

View File

@ -55974,6 +55974,14 @@
"default": "", "default": "",
"description": "An array of treenodes." "description": "An array of treenodes."
}, },
{
"name": "dataKey",
"optional": true,
"readonly": false,
"type": "string | Function",
"default": "\"key\"",
"description": "Name of the field that uniquely identifies the a record in the data."
},
{ {
"name": "expandedKeys", "name": "expandedKeys",
"optional": true, "optional": true,

View File

@ -223,7 +223,7 @@ export default {
this.$toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 }); this.$toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 });
}, },
expandAll() { expandAll() {
this.expandedRows = this.products.filter((p) => p.id); this.expandedRows = this.products.reduce((acc, p) => (acc[p.id] = true) && acc, {});
}, },
collapseAll() { collapseAll() {
this.expandedRows = null; this.expandedRows = null;
@ -352,7 +352,7 @@ const onRowCollapse = (event) => {
toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 }); toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 });
}; };
const expandAll = () => { const expandAll = () => {
expandedRows.value = products.value.filter((p) => p.id); expandedRows.value = products.value.reduce((acc, p) => (acc[p.id] = true) && acc, {});
}; };
const collapseAll = () => { const collapseAll = () => {
expandedRows.value = null; expandedRows.value = null;
@ -437,7 +437,7 @@ const getOrderSeverity = (order) => {
this.$toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 }); this.$toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 });
}, },
expandAll() { expandAll() {
this.expandedRows = this.products.filter((p) => p.id); this.expandedRows = this.products.reduce((acc, p) => (acc[p.id] = true) && acc, {});
}, },
collapseAll() { collapseAll() {
this.expandedRows = null; this.expandedRows = null;

View File

@ -1,7 +1,9 @@
<template> <template>
<DocSectionText v-bind="$attrs"> <DocSectionText v-bind="$attrs">
<p>Selection of multiple nodes via checkboxes is enabled by configuring <i>selectionMode</i> as <i>checkbox</i>.</p> <p>Selection of multiple nodes via checkboxes is enabled by configuring <i>selectionMode</i> as <i>checkbox</i>.</p>
<p>In checkbox selection mode, value binding should be a key-value pair where key is the node key and value is an object that has <i>checked</i> and <i>partialChecked</i> properties to represent the checked state of a node.</p> <p>
In checkbox selection mode, value binding should be a key-value pair where key (or the dataKey) is the node key and value is an object that has <i>checked</i> and <i>partialChecked</i> properties to represent the checked state of a node.
</p>
</DocSectionText> </DocSectionText>
<DocSectionCode :code="introCode" hideToggleCode importCode hideCodeSandbox hideStackBlitz /> <DocSectionCode :code="introCode" hideToggleCode importCode hideCodeSandbox hideStackBlitz />
<div class="card"> <div class="card">

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "primevue", "name": "primevue",
"version": "3.44.0", "version": "3.44.1-SNAPSHOT",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "primevue", "name": "primevue",
"version": "3.44.0", "version": "3.44.1-SNAPSHOT",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {