Migrated TreeTable to V3

pull/496/head
Cagatay Civici 2020-09-24 14:02:09 +03:00
parent d32c681ce1
commit 0db8f3f17c
6 changed files with 95 additions and 189 deletions

View File

@ -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,
}
}

View File

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

View File

@ -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
}

View File

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

View File

@ -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;

View File

@ -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 @@
&lt;Column field="name" header="Name" :expander="true"&gt;&lt;/Column&gt;
&lt;Column field="size" header="Size"&gt;&lt;/Column&gt;
&lt;Column field="type" header="Type"&gt;&lt;/Column&gt;
&lt;Column headerStyle="width: 8em" bodyStyle="text-align: center"&gt;
&lt;Column headerStyle="width: 8em" headerClass="p-text-center" bodyClass="p-text-center"&gt;
&lt;template #header&gt;
&lt;Button type="button" icon="pi pi-cog"&gt;&lt;/Button&gt;
&lt;/template&gt;
&lt;template&gt;
&lt;template #body&gt;
&lt;Button type="button" icon="pi pi-search" class="p-button-success" style="margin-right: .5em"&gt;&lt;/Button&gt;
&lt;Button type="button" icon="pi pi-pencil" class="p-button-warning"&gt;&lt;/Button&gt;
&lt;/template&gt;