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> <template>
<div :class="containerClass"> <div :class="containerClass">
<slot></slot>
<div class="p-treetable-loading" v-if="loading"> <div class="p-treetable-loading" v-if="loading">
<div class="p-treetable-loading-overlay p-component-overlay"> <div class="p-treetable-loading-overlay p-component-overlay">
<i :class="loadingIconClass"></i> <i :class="loadingIconClass"></i>
@ -22,28 +21,26 @@
<table ref="table"> <table ref="table">
<thead class="p-treetable-thead"> <thead class="p-treetable-thead">
<tr> <tr>
<th v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.headerStyle" :class="getColumnHeaderClass(col)" @click="onColumnHeaderClick($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.sortable ? '0' : null" :aria-sort="getAriaSort(col)" @keydown="onColumnKeyDown($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> <span class="p-column-resizer" @mousedown="onColumnResizeStart" v-if="resizableColumns"></span>
<TTColumnSlot :column="col" type="header" v-if="col.$scopedSlots.header" /> <component :is="col.children?.header" :column="col" />
<span class="p-column-title" v-if="col.header">{{col.header}}</span> <span class="p-column-title" v-if="col.props?.header">{{col.props?.header}}</span>
<span v-if="col.sortable" :class="getSortableColumnIcon(col)"></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> <span v-if="isMultiSorted(col)" class="p-sortable-column-badge">{{getMultiSortMetaIndex(col) + 1}}</span>
</th> </th>
</tr> </tr>
<tr v-if="hasColumnFilter()"> <tr v-if="hasColumnFilter()">
<template v-for="(col,i) of columns" :key="col.columnKey||col.field||i"> <th v-for="(col,i) of columns" :key="col.props?.columnKey||col.props?.field||i" :class="getFilterColumnHeaderClass(col)" :style="col.props?.filterHeaderStyle">
<th :class="getFilterColumnHeaderClass(col)" :style="col.filterHeaderStyle"> <component :is="col.children?.filter" :column="col" v-if="col.children?.filter"/>
<TTColumnSlot :column="col" type="filter" v-if="col.$scopedSlots.filter" />
</th> </th>
</template>
</tr> </tr>
</thead> </thead>
<tfoot class="p-treetable-tfoot" v-if="hasFooter"> <tfoot class="p-treetable-tfoot" v-if="hasFooter">
<tr> <tr>
<td v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.footerStyle" :class="col.footerClass"> <td v-for="(col,i) of columns" :key="col.props?.columnKey||col.props?.field||i" :style="col.props?.footerStyle" :class="col.props?.footerClass">
<TTColumnSlot :column="col" type="footer" v-if="col.$scopedSlots.footer" /> <component :is="col.children?.footer" :column="col" />
{{col.footer}} {{col.props?.footer}}
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
@ -81,8 +78,7 @@
import ObjectUtils from '../utils/ObjectUtils'; import ObjectUtils from '../utils/ObjectUtils';
import FilterUtils from '../utils/FilterUtils'; import FilterUtils from '../utils/FilterUtils';
import DomHandler from '../utils/DomHandler'; import DomHandler from '../utils/DomHandler';
import TreeTableColumnSlot from './TreeTableColumnSlot'; import TreeTableRow from './TreeTableRow';
import TreeTableRowLoader from './TreeTableRowLoader';
import Paginator from '../paginator/Paginator'; import Paginator from '../paginator/Paginator';
export default { export default {
@ -218,7 +214,6 @@ export default {
resizeColumnElement: null, resizeColumnElement: null,
data() { data() {
return { return {
allChildren: null,
d_expandedKeys: this.expandedKeys || {}, d_expandedKeys: this.expandedKeys || {},
d_first: this.first, d_first: this.first,
d_rows: this.rows, d_rows: this.rows,
@ -247,9 +242,6 @@ export default {
this.d_multiSortMeta = newValue; this.d_multiSortMeta = newValue;
} }
}, },
mounted() {
this.allChildren = this.$children;
},
methods: { methods: {
onNodeToggle(node) { onNodeToggle(node) {
const key = node.key; const key = node.key;
@ -370,31 +362,31 @@ export default {
this.$emit('update:first', this.d_first); this.$emit('update:first', this.d_first);
}, },
isMultiSorted(column) { isMultiSorted(column) {
return column.sortable && this.getMultiSortMetaIndex(column) > -1 return column.props?.sortable && this.getMultiSortMetaIndex(column) > -1
}, },
isColumnSorted(column) { isColumnSorted(column) {
if (column.sortable) { if (column.props?.sortable) {
return this.sortMode === 'single' ? (this.d_sortField === (column.field || column.sortField)) : this.getMultiSortMetaIndex(column) > -1; return this.sortMode === 'single' ? (this.d_sortField === (column.props?.field || column.props?.sortField)) : this.getMultiSortMetaIndex(column) > -1;
} }
return false; return false;
}, },
getColumnHeaderClass(column) { getColumnHeaderClass(column) {
return [column.headerClass, return [column.props?.headerClass,
{'p-sortable-column': column.sortable}, {'p-sortable-column': column.props?.sortable},
{'p-resizable-column': this.resizableColumns}, {'p-resizable-column': this.resizableColumns},
{'p-highlight': this.isColumnSorted(column)} {'p-highlight': this.isColumnSorted(column)}
]; ];
}, },
getFilterColumnHeaderClass(column) { getFilterColumnHeaderClass(column) {
return ['p-filter-column', column.filterHeaderClass]; return ['p-filter-column', column.props?.filterHeaderClass];
}, },
getSortableColumnIcon(column) { getSortableColumnIcon(column) {
let sorted = false; let sorted = false;
let sortOrder = null; let sortOrder = null;
if (this.sortMode === 'single') { 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; sortOrder = sorted ? this.d_sortOrder: 0;
} }
else if (this.sortMode === 'multiple') { else if (this.sortMode === 'multiple') {
@ -418,7 +410,7 @@ export default {
for (let i = 0; i < this.d_multiSortMeta.length; i++) { for (let i = 0; i < this.d_multiSortMeta.length; i++) {
let meta = this.d_multiSortMeta[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; index = i;
break; break;
} }
@ -427,9 +419,9 @@ export default {
return index; return index;
}, },
onColumnHeaderClick(event, column) { onColumnHeaderClick(event, column) {
if (column.sortable) { if (column.props?.sortable) {
const targetNode = event.target; 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') 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')) { || 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++) { for (let j = 0; j < this.columns.length; j++) {
let col = this.columns[j]; let col = this.columns[j];
let filterField = col.field; let filterField = col.props?.field;
//local //local
if (Object.prototype.hasOwnProperty.call(this.filters, col.field)) { if (Object.prototype.hasOwnProperty.call(this.filters, col.props?.field)) {
let filterMatchMode = col.filterMatchMode; let filterMatchMode = col.props?.filterMatchMode || 'startsWith';
let filterValue = this.filters[col.field]; let filterValue = this.filters[col.props?.field];
let filterConstraint = FilterUtils[filterMatchMode]; let filterConstraint = FilterUtils[filterMatchMode];
let paramsWithoutNode = {filterField, filterValue, filterConstraint, strict}; let paramsWithoutNode = {filterField, filterValue, filterConstraint, strict};
@ -651,8 +643,8 @@ export default {
if (this.hasFilters) { if (this.hasFilters) {
filterMatchModes = {}; filterMatchModes = {};
this.columns.forEach(col => { this.columns.forEach(col => {
if (col.field) { if (col.props?.field) {
filterMatchModes[col.field] = col.filterMatchMode; filterMatchModes[col.props?.field] = col.props?.filterMatchMode;
} }
}); });
} }
@ -756,7 +748,7 @@ export default {
} }
}, },
getAriaSort(column) { getAriaSort(column) {
if (column.sortable) { if (column.props?.sortable) {
const sortIcon = this.getSortableColumnIcon(column); const sortIcon = this.getSortableColumnIcon(column);
if (sortIcon[1]['pi-sort-amount-down']) if (sortIcon[1]['pi-sort-amount-down'])
return 'descending'; return 'descending';
@ -772,7 +764,7 @@ export default {
hasColumnFilter() { hasColumnFilter() {
if (this.columns) { if (this.columns) {
for (let col of this.columns) { for (let col of this.columns) {
if (col.$scopedSlots.filter) { if (col.children?.filter) {
return true; return true;
} }
} }
@ -791,10 +783,17 @@ export default {
}]; }];
}, },
columns() { columns() {
if (this.allChildren) { let cols = [];
return this.allChildren.filter(child => child.$options._propKeys.indexOf('columnKey') !== -1); let children = this.$slots.default();
}
return []; children.forEach(child => {
if (child.dynamicChildren)
cols = [...cols, ...child.children];
else if (child.type.name === 'column')
cols.push(child);
});
return cols;
}, },
processedData() { processedData() {
if (this.lazy) { if (this.lazy) {
@ -844,7 +843,7 @@ export default {
let hasFooter = false; let hasFooter = false;
for (let col of this.columns) { for (let col of this.columns) {
if (col.footer || col.$scopedSlots.footer) { if (col.props?.footer || col.children?.footer) {
hasFooter = true; hasFooter = true;
break; break;
} }
@ -887,8 +886,7 @@ export default {
} }
}, },
components: { components: {
'TTColumnSlot': TreeTableColumnSlot, 'TTRow': TreeTableRow,
'TTRow': TreeTableRowLoader,
'TTPaginator': Paginator, '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> <template>
<tr :class="containerClass" @click="onClick" @keydown="onKeyDown" @touchend="onTouchEnd" :style="node.style" tabindex="0"> <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"> <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.expander" :style="togglerStyle" tabindex="-1" v-ripple> <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> <i :class="togglerIcon"></i>
</button> </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"> <div class="p-hidden-accessible">
<input type="checkbox" @focus="onCheckboxFocus" @blur="onCheckboxBlur" /> <input type="checkbox" @focus="onCheckboxFocus" @blur="onCheckboxBlur" />
</div> </div>
@ -12,20 +12,25 @@
<span :class="checkboxIcon"></span> <span :class="checkboxIcon"></span>
</div> </div>
</div> </div>
<TTColumnSlot :node="node" :column="col" type="body" v-if="col.$scopedSlots.body" /> <component :is="col.children?.body" :node="node" :column="col" v-if="col.children?.body" />
<template v-else><span>{{resolveFieldData(node.data, col.field)}}</span></template> <template v-else><span>{{resolveFieldData(node.data, col.props?.field)}}</span></template>
</td> </td>
</tr> </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> </template>
<script> <script>
import ObjectUtils from '../utils/ObjectUtils'; import ObjectUtils from '../utils/ObjectUtils';
import DomHandler from '../utils/DomHandler'; import DomHandler from '../utils/DomHandler';
import TreeTableColumnSlot from './TreeTableColumnSlot';
import Ripple from '../ripple/Ripple'; import Ripple from '../ripple/Ripple';
export default { export default {
name: 'sub-ttnode', name: 'sub-ttnode',
emits: ['node-click', 'node-toggle', 'checkbox-change'],
props: { props: {
node: { node: {
type: null, type: null,
@ -198,6 +203,39 @@ export default {
}, },
onCheckboxBlur() { onCheckboxBlur() {
this.checkboxFocused = false; 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: { computed: {
@ -246,9 +284,6 @@ export default {
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].partialChecked: false; return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].partialChecked: false;
} }
}, },
components: {
'TTColumnSlot': TreeTableColumnSlot
},
directives: { directives: {
'ripple': Ripple '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) { @media screen and (max-width: 40em) {
::v-deep(.sm-invisible) {
display: none;
}
::v-deep(.sm-visible) { ::v-deep(.sm-visible) {
display: inline; display: inline;
margin-right: .5rem; margin-right: .5rem;

View File

@ -16,11 +16,11 @@
<Column field="name" header="Name" :expander="true"></Column> <Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column> <Column field="size" header="Size"></Column>
<Column field="type" header="Type"></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> <template #header>
<Button type="button" icon="pi pi-cog"></Button> <Button type="button" icon="pi pi-cog"></Button>
</template> </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-search" class="p-button-success" style="margin-right: .5em"></Button>
<Button type="button" icon="pi pi-pencil" class="p-button-warning"></Button> <Button type="button" icon="pi pi-pencil" class="p-button-warning"></Button>
</template> </template>
@ -46,11 +46,11 @@
&lt;Column field="name" header="Name" :expander="true"&gt;&lt;/Column&gt; &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="size" header="Size"&gt;&lt;/Column&gt;
&lt;Column field="type" header="Type"&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;template #header&gt;
&lt;Button type="button" icon="pi pi-cog"&gt;&lt;/Button&gt; &lt;Button type="button" icon="pi pi-cog"&gt;&lt;/Button&gt;
&lt;/template&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-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;Button type="button" icon="pi pi-pencil" class="p-button-warning"&gt;&lt;/Button&gt;
&lt;/template&gt; &lt;/template&gt;