Migrated TreeTable to V3
parent
d32c681ce1
commit
0db8f3f17c
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div :class="containerClass">
|
||||
<slot></slot>
|
||||
<div class="p-treetable-loading" v-if="loading">
|
||||
<div class="p-treetable-loading-overlay p-component-overlay">
|
||||
<i :class="loadingIconClass"></i>
|
||||
|
@ -22,28 +21,26 @@
|
|||
<table ref="table">
|
||||
<thead class="p-treetable-thead">
|
||||
<tr>
|
||||
<th v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.headerStyle" :class="getColumnHeaderClass(col)" @click="onColumnHeaderClick($event, col)"
|
||||
:tabindex="col.sortable ? '0' : null" :aria-sort="getAriaSort(col)" @keydown="onColumnKeyDown($event, col)">
|
||||
<th v-for="(col,i) of columns" :key="col.props?.columnKey||col.props?.field||i" :style="col.props?.headerStyle" :class="getColumnHeaderClass(col)" @click="onColumnHeaderClick($event, col)"
|
||||
:tabindex="col.props?.sortable ? '0' : null" :aria-sort="getAriaSort(col)" @keydown="onColumnKeyDown($event, col)">
|
||||
<span class="p-column-resizer" @mousedown="onColumnResizeStart" v-if="resizableColumns"></span>
|
||||
<TTColumnSlot :column="col" type="header" v-if="col.$scopedSlots.header" />
|
||||
<span class="p-column-title" v-if="col.header">{{col.header}}</span>
|
||||
<span v-if="col.sortable" :class="getSortableColumnIcon(col)"></span>
|
||||
<component :is="col.children?.header" :column="col" />
|
||||
<span class="p-column-title" v-if="col.props?.header">{{col.props?.header}}</span>
|
||||
<span v-if="col.props?.sortable" :class="getSortableColumnIcon(col)"></span>
|
||||
<span v-if="isMultiSorted(col)" class="p-sortable-column-badge">{{getMultiSortMetaIndex(col) + 1}}</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr v-if="hasColumnFilter()">
|
||||
<template v-for="(col,i) of columns" :key="col.columnKey||col.field||i">
|
||||
<th :class="getFilterColumnHeaderClass(col)" :style="col.filterHeaderStyle">
|
||||
<TTColumnSlot :column="col" type="filter" v-if="col.$scopedSlots.filter" />
|
||||
</th>
|
||||
</template>
|
||||
<th v-for="(col,i) of columns" :key="col.props?.columnKey||col.props?.field||i" :class="getFilterColumnHeaderClass(col)" :style="col.props?.filterHeaderStyle">
|
||||
<component :is="col.children?.filter" :column="col" v-if="col.children?.filter"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot class="p-treetable-tfoot" v-if="hasFooter">
|
||||
<tr>
|
||||
<td v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.footerStyle" :class="col.footerClass">
|
||||
<TTColumnSlot :column="col" type="footer" v-if="col.$scopedSlots.footer" />
|
||||
{{col.footer}}
|
||||
<td v-for="(col,i) of columns" :key="col.props?.columnKey||col.props?.field||i" :style="col.props?.footerStyle" :class="col.props?.footerClass">
|
||||
<component :is="col.children?.footer" :column="col" />
|
||||
{{col.props?.footer}}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
@ -81,8 +78,7 @@
|
|||
import ObjectUtils from '../utils/ObjectUtils';
|
||||
import FilterUtils from '../utils/FilterUtils';
|
||||
import DomHandler from '../utils/DomHandler';
|
||||
import TreeTableColumnSlot from './TreeTableColumnSlot';
|
||||
import TreeTableRowLoader from './TreeTableRowLoader';
|
||||
import TreeTableRow from './TreeTableRow';
|
||||
import Paginator from '../paginator/Paginator';
|
||||
|
||||
export default {
|
||||
|
@ -218,7 +214,6 @@ export default {
|
|||
resizeColumnElement: null,
|
||||
data() {
|
||||
return {
|
||||
allChildren: null,
|
||||
d_expandedKeys: this.expandedKeys || {},
|
||||
d_first: this.first,
|
||||
d_rows: this.rows,
|
||||
|
@ -247,9 +242,6 @@ export default {
|
|||
this.d_multiSortMeta = newValue;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.allChildren = this.$children;
|
||||
},
|
||||
methods: {
|
||||
onNodeToggle(node) {
|
||||
const key = node.key;
|
||||
|
@ -370,31 +362,31 @@ export default {
|
|||
this.$emit('update:first', this.d_first);
|
||||
},
|
||||
isMultiSorted(column) {
|
||||
return column.sortable && this.getMultiSortMetaIndex(column) > -1
|
||||
return column.props?.sortable && this.getMultiSortMetaIndex(column) > -1
|
||||
},
|
||||
isColumnSorted(column) {
|
||||
if (column.sortable) {
|
||||
return this.sortMode === 'single' ? (this.d_sortField === (column.field || column.sortField)) : this.getMultiSortMetaIndex(column) > -1;
|
||||
if (column.props?.sortable) {
|
||||
return this.sortMode === 'single' ? (this.d_sortField === (column.props?.field || column.props?.sortField)) : this.getMultiSortMetaIndex(column) > -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getColumnHeaderClass(column) {
|
||||
return [column.headerClass,
|
||||
{'p-sortable-column': column.sortable},
|
||||
return [column.props?.headerClass,
|
||||
{'p-sortable-column': column.props?.sortable},
|
||||
{'p-resizable-column': this.resizableColumns},
|
||||
{'p-highlight': this.isColumnSorted(column)}
|
||||
];
|
||||
},
|
||||
getFilterColumnHeaderClass(column) {
|
||||
return ['p-filter-column', column.filterHeaderClass];
|
||||
return ['p-filter-column', column.props?.filterHeaderClass];
|
||||
},
|
||||
getSortableColumnIcon(column) {
|
||||
let sorted = false;
|
||||
let sortOrder = null;
|
||||
|
||||
if (this.sortMode === 'single') {
|
||||
sorted = this.d_sortField === (column.field || column.sortField);
|
||||
sorted = this.d_sortField === (column.props?.field || column.props?.sortField);
|
||||
sortOrder = sorted ? this.d_sortOrder: 0;
|
||||
}
|
||||
else if (this.sortMode === 'multiple') {
|
||||
|
@ -418,7 +410,7 @@ export default {
|
|||
|
||||
for (let i = 0; i < this.d_multiSortMeta.length; i++) {
|
||||
let meta = this.d_multiSortMeta[i];
|
||||
if (meta.field === (column.field || column.sortField)) {
|
||||
if (meta.field === (column.props?.field || column.props?.sortField)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
@ -427,9 +419,9 @@ export default {
|
|||
return index;
|
||||
},
|
||||
onColumnHeaderClick(event, column) {
|
||||
if (column.sortable) {
|
||||
if (column.props?.sortable) {
|
||||
const targetNode = event.target;
|
||||
const columnField = column.sortField || column.field;
|
||||
const columnField = column.props?.sortField || column.props?.field;
|
||||
|
||||
if (DomHandler.hasClass(targetNode, 'p-sortable-column') || DomHandler.hasClass(targetNode, 'p-column-title')
|
||||
|| DomHandler.hasClass(targetNode, 'p-sortable-column-icon') || DomHandler.hasClass(targetNode.parentElement, 'p-sortable-column-icon')) {
|
||||
|
@ -557,12 +549,12 @@ export default {
|
|||
|
||||
for (let j = 0; j < this.columns.length; j++) {
|
||||
let col = this.columns[j];
|
||||
let filterField = col.field;
|
||||
let filterField = col.props?.field;
|
||||
|
||||
//local
|
||||
if (Object.prototype.hasOwnProperty.call(this.filters, col.field)) {
|
||||
let filterMatchMode = col.filterMatchMode;
|
||||
let filterValue = this.filters[col.field];
|
||||
if (Object.prototype.hasOwnProperty.call(this.filters, col.props?.field)) {
|
||||
let filterMatchMode = col.props?.filterMatchMode || 'startsWith';
|
||||
let filterValue = this.filters[col.props?.field];
|
||||
let filterConstraint = FilterUtils[filterMatchMode];
|
||||
let paramsWithoutNode = {filterField, filterValue, filterConstraint, strict};
|
||||
|
||||
|
@ -651,8 +643,8 @@ export default {
|
|||
if (this.hasFilters) {
|
||||
filterMatchModes = {};
|
||||
this.columns.forEach(col => {
|
||||
if (col.field) {
|
||||
filterMatchModes[col.field] = col.filterMatchMode;
|
||||
if (col.props?.field) {
|
||||
filterMatchModes[col.props?.field] = col.props?.filterMatchMode;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -756,7 +748,7 @@ export default {
|
|||
}
|
||||
},
|
||||
getAriaSort(column) {
|
||||
if (column.sortable) {
|
||||
if (column.props?.sortable) {
|
||||
const sortIcon = this.getSortableColumnIcon(column);
|
||||
if (sortIcon[1]['pi-sort-amount-down'])
|
||||
return 'descending';
|
||||
|
@ -772,7 +764,7 @@ export default {
|
|||
hasColumnFilter() {
|
||||
if (this.columns) {
|
||||
for (let col of this.columns) {
|
||||
if (col.$scopedSlots.filter) {
|
||||
if (col.children?.filter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -791,10 +783,17 @@ export default {
|
|||
}];
|
||||
},
|
||||
columns() {
|
||||
if (this.allChildren) {
|
||||
return this.allChildren.filter(child => child.$options._propKeys.indexOf('columnKey') !== -1);
|
||||
}
|
||||
return [];
|
||||
let cols = [];
|
||||
let children = this.$slots.default();
|
||||
|
||||
children.forEach(child => {
|
||||
if (child.dynamicChildren)
|
||||
cols = [...cols, ...child.children];
|
||||
else if (child.type.name === 'column')
|
||||
cols.push(child);
|
||||
});
|
||||
|
||||
return cols;
|
||||
},
|
||||
processedData() {
|
||||
if (this.lazy) {
|
||||
|
@ -844,7 +843,7 @@ export default {
|
|||
let hasFooter = false;
|
||||
|
||||
for (let col of this.columns) {
|
||||
if (col.footer || col.$scopedSlots.footer) {
|
||||
if (col.props?.footer || col.children?.footer) {
|
||||
hasFooter = true;
|
||||
break;
|
||||
}
|
||||
|
@ -887,8 +886,7 @@ export default {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
'TTColumnSlot': TreeTableColumnSlot,
|
||||
'TTRow': TreeTableRowLoader,
|
||||
'TTRow': TreeTableRow,
|
||||
'TTPaginator': Paginator,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
functional: true,
|
||||
props: {
|
||||
column: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
node: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
render(createElement, context) {
|
||||
const content = context.props.column.$scopedSlots[context.props.type]({
|
||||
'node': context.props.node,
|
||||
'column': context.props.column
|
||||
});
|
||||
return [content];
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<tr :class="containerClass" @click="onClick" @keydown="onKeyDown" @touchend="onTouchEnd" :style="node.style" tabindex="0">
|
||||
<td v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.bodyStyle" :class="col.bodyClass">
|
||||
<button type="button" class="p-treetable-toggler p-link" @click="toggle" v-if="col.expander" :style="togglerStyle" tabindex="-1" v-ripple>
|
||||
<td v-for="(col,i) of columns" :key="col.props?.columnKey||col.props?.field||i" :style="col.props?.bodyStyle" :class="col.props?.bodyClass">
|
||||
<button type="button" class="p-treetable-toggler p-link" @click="toggle" v-if="col.props?.expander" :style="togglerStyle" tabindex="-1" v-ripple>
|
||||
<i :class="togglerIcon"></i>
|
||||
</button>
|
||||
<div class="p-checkbox p-treetable-checkbox p-component" @click="toggleCheckbox" v-if="checkboxSelectionMode && col.expander" role="checkbox" :aria-checked="checked">
|
||||
<div class="p-checkbox p-treetable-checkbox p-component" @click="toggleCheckbox" v-if="checkboxSelectionMode && col.props?.expander" role="checkbox" :aria-checked="checked">
|
||||
<div class="p-hidden-accessible">
|
||||
<input type="checkbox" @focus="onCheckboxFocus" @blur="onCheckboxBlur" />
|
||||
</div>
|
||||
|
@ -12,20 +12,25 @@
|
|||
<span :class="checkboxIcon"></span>
|
||||
</div>
|
||||
</div>
|
||||
<TTColumnSlot :node="node" :column="col" type="body" v-if="col.$scopedSlots.body" />
|
||||
<template v-else><span>{{resolveFieldData(node.data, col.field)}}</span></template>
|
||||
<component :is="col.children?.body" :node="node" :column="col" v-if="col.children?.body" />
|
||||
<template v-else><span>{{resolveFieldData(node.data, col.props?.field)}}</span></template>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="expanded && node.children && node.children.length">
|
||||
<sub-ttnode v-for="childNode of node.children" :key="childNode.key" :columns="columns" :node="childNode" :parentNode="node" :level="level + 1"
|
||||
:expandedKeys="expandedKeys" :selectionMode="selectionMode" :selectionKeys="selectionKeys"
|
||||
@node-toggle="$emit('node-toggle', $event)" @node-click="$emit('node-click', $event)" @checkbox-change="onCheckboxChange"></sub-ttnode>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ObjectUtils from '../utils/ObjectUtils';
|
||||
import DomHandler from '../utils/DomHandler';
|
||||
import TreeTableColumnSlot from './TreeTableColumnSlot';
|
||||
import Ripple from '../ripple/Ripple';
|
||||
|
||||
export default {
|
||||
name: 'sub-ttnode',
|
||||
emits: ['node-click', 'node-toggle', 'checkbox-change'],
|
||||
props: {
|
||||
node: {
|
||||
type: null,
|
||||
|
@ -198,6 +203,39 @@ export default {
|
|||
},
|
||||
onCheckboxBlur() {
|
||||
this.checkboxFocused = false;
|
||||
},
|
||||
onCheckboxChange(event) {
|
||||
let check = event.check;
|
||||
let _selectionKeys = {...event.selectionKeys};
|
||||
let checkedChildCount = 0;
|
||||
let childPartialSelected = false;
|
||||
|
||||
for(let child of this.node.children) {
|
||||
if(_selectionKeys[child.key] && _selectionKeys[child.key].checked)
|
||||
checkedChildCount++;
|
||||
else if(_selectionKeys[child.key] && _selectionKeys[child.key].partialChecked)
|
||||
childPartialSelected = true;
|
||||
}
|
||||
|
||||
if(check && checkedChildCount === this.node.children.length) {
|
||||
_selectionKeys[this.node.key] = {checked: true, partialChecked: false};
|
||||
}
|
||||
else {
|
||||
if (!check) {
|
||||
delete _selectionKeys[this.node.key];
|
||||
}
|
||||
|
||||
if(childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length))
|
||||
_selectionKeys[this.node.key] = {checked: false, partialChecked: true};
|
||||
else
|
||||
_selectionKeys[this.node.key] = {checked: false, partialChecked: false};
|
||||
}
|
||||
|
||||
this.$emit('checkbox-change', {
|
||||
node: event.node,
|
||||
check: event.check,
|
||||
selectionKeys: _selectionKeys
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -246,9 +284,6 @@ export default {
|
|||
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].partialChecked: false;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'TTColumnSlot': TreeTableColumnSlot
|
||||
},
|
||||
directives: {
|
||||
'ripple': Ripple
|
||||
}
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
<script>
|
||||
import TreeTableRow from './TreeTableRow';
|
||||
|
||||
const TreeTableRowLoader = {
|
||||
functional: true,
|
||||
props: {
|
||||
node: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
columns: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
expandedKeys: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
selectionKeys: {
|
||||
type: null,
|
||||
default: null
|
||||
},
|
||||
selectionMode: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
level: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
render(createElement, context) {
|
||||
const root = createElement(TreeTableRow, {
|
||||
key: context.props.node.key,
|
||||
props: context.props,
|
||||
on: {
|
||||
'node-toggle': context.listeners['node-toggle'],
|
||||
'node-click': context.listeners['node-click'],
|
||||
'checkbox-change': context.listeners['checkbox-change']
|
||||
}
|
||||
});
|
||||
|
||||
let element = [root];
|
||||
const node = context.props.node;
|
||||
const expanded = context.props.expandedKeys && context.props.expandedKeys[node.key] === true;
|
||||
|
||||
if (expanded && node.children && node.children.length) {
|
||||
for (let childNode of node.children) {
|
||||
let childNodeProps = {...context.props};
|
||||
childNodeProps.node = childNode;
|
||||
childNodeProps.parentNode = node;
|
||||
childNodeProps.level = context.props.level + 1;
|
||||
|
||||
let childNodeElement = createElement(TreeTableRowLoader, {
|
||||
key: childNode.key,
|
||||
props: childNodeProps,
|
||||
on: {
|
||||
'node-toggle': context.listeners['node-toggle'],
|
||||
'node-click': context.listeners['node-click'],
|
||||
'checkbox-change': (event) => {
|
||||
let check = event.check;
|
||||
let _selectionKeys = {...event.selectionKeys};
|
||||
let checkedChildCount = 0;
|
||||
let childPartialSelected = false;
|
||||
|
||||
for(let child of node.children) {
|
||||
if(_selectionKeys[child.key] && _selectionKeys[child.key].checked)
|
||||
checkedChildCount++;
|
||||
else if(_selectionKeys[child.key] && _selectionKeys[child.key].partialChecked)
|
||||
childPartialSelected = true;
|
||||
}
|
||||
|
||||
if(check && checkedChildCount === node.children.length) {
|
||||
_selectionKeys[node.key] = {checked: true, partialChecked: false};
|
||||
}
|
||||
else {
|
||||
if (!check) {
|
||||
delete _selectionKeys[node.key];
|
||||
}
|
||||
|
||||
if(childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== node.children.length))
|
||||
_selectionKeys[node.key] = {checked: false, partialChecked: true};
|
||||
else
|
||||
_selectionKeys[node.key] = {checked: false, partialChecked: false};
|
||||
}
|
||||
|
||||
context.listeners['checkbox-change']({
|
||||
node: event.node,
|
||||
check: event.check,
|
||||
selectionKeys: _selectionKeys
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
element.push(childNodeElement);
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
};
|
||||
|
||||
export default TreeTableRowLoader;
|
||||
</script>
|
|
@ -111,6 +111,10 @@ export default {
|
|||
}
|
||||
|
||||
@media screen and (max-width: 40em) {
|
||||
::v-deep(.sm-invisible) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep(.sm-visible) {
|
||||
display: inline;
|
||||
margin-right: .5rem;
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
<Column field="name" header="Name" :expander="true"></Column>
|
||||
<Column field="size" header="Size"></Column>
|
||||
<Column field="type" header="Type"></Column>
|
||||
<Column headerStyle="width: 8em" bodyStyle="text-align: center">
|
||||
<Column headerStyle="width: 8em" headerClass="p-text-center" bodyClass="p-text-center">
|
||||
<template #header>
|
||||
<Button type="button" icon="pi pi-cog"></Button>
|
||||
</template>
|
||||
<template>
|
||||
<template #body>
|
||||
<Button type="button" icon="pi pi-search" class="p-button-success" style="margin-right: .5em"></Button>
|
||||
<Button type="button" icon="pi pi-pencil" class="p-button-warning"></Button>
|
||||
</template>
|
||||
|
@ -46,11 +46,11 @@
|
|||
<Column field="name" header="Name" :expander="true"></Column>
|
||||
<Column field="size" header="Size"></Column>
|
||||
<Column field="type" header="Type"></Column>
|
||||
<Column headerStyle="width: 8em" bodyStyle="text-align: center">
|
||||
<Column headerStyle="width: 8em" headerClass="p-text-center" bodyClass="p-text-center">
|
||||
<template #header>
|
||||
<Button type="button" icon="pi pi-cog"></Button>
|
||||
</template>
|
||||
<template>
|
||||
<template #body>
|
||||
<Button type="button" icon="pi pi-search" class="p-button-success" style="margin-right: .5em"></Button>
|
||||
<Button type="button" icon="pi pi-pencil" class="p-button-warning"></Button>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue