Fixed #338 - Scrollable TreeTable

pull/1196/head^2
Cagatay Civici 2021-05-15 12:45:36 +03:00
parent 8329ed5423
commit 60547bc237
11 changed files with 702 additions and 190 deletions

View File

@ -196,6 +196,24 @@ const TreeTableProps = [
type: "boolean",
default: "false",
description: "Whether to show grid lines between cells."
},
{
name: "scrollable",
type: "boolean",
default: "false",
description: "When specified, enables horizontal and/or vertical scrolling."
},
{
name: "scrollDirection",
type: "string",
default: "vertical",
description: 'Orientation of the scrolling, options are "vertical", "horizontal" and "both".'
},
{
name: "scrollHeight",
type: "string",
default: "null",
description: 'Height of the scroll viewport in fixed units or the "flex" keyword for a dynamic size.'
}
];

View File

@ -53,6 +53,7 @@ interface DataTableProps {
rowClass?: any;
scrollable?: boolean;
scrollHeight?: string;
scrollDirection?: string;
frozenValue?: any[];
responsiveLayout?: string;
breakpoing?: string;

View File

@ -0,0 +1,157 @@
<template>
<td :style="containerStyle" :class="containerClass">
<button type="button" class="p-treetable-toggler p-link" @click="toggle" v-if="columnProp('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 && columnProp('expander')" role="checkbox" :aria-checked="checked">
<div class="p-hidden-accessible">
<input type="checkbox" @focus="onCheckboxFocus" @blur="onCheckboxBlur" />
</div>
<div ref="checkboxEl" :class="checkboxClass">
<span :class="checkboxIcon"></span>
</div>
</div>
<component :is="column.children.body" :node="node" :column="column" v-if="column.children && column.children.body" />
<template v-else><span>{{resolveFieldData(node.data, columnProp('field'))}}</span></template>
</td>
</template>
<script>
import {DomHandler} from 'primevue/utils';
import {ObjectUtils} from 'primevue/utils';
import Ripple from 'primevue/ripple'
export default {
name: 'BodyCell',
emits: ['node-toggle','checkbox-toggle'],
props: {
node: {
type: Object,
default: null
},
column: {
type: Object,
default: null
},
level: {
type: Number,
default: 0
},
indentation: {
type: Number,
default: 1
},
leaf: {
type: Boolean,
default: false
},
expanded: {
type: Boolean,
default: false
},
selectionMode: {
type: String,
default: null
},
checked: {
type: Boolean,
default: false
},
partialChecked: {
type: Boolean,
default: false
}
},
data() {
return {
styleObject: {},
checkboxFocused: false
}
},
mounted() {
if (this.columnProp('frozen')) {
this.updateStickyPosition();
}
},
updated() {
if (this.columnProp('frozen')) {
this.updateStickyPosition();
}
},
methods: {
toggle() {
this.$emit('node-toggle', this.node);
},
columnProp(prop) {
return this.column.props ? ((this.column.type.props[prop].type === Boolean && this.column.props[prop] === '') ? true : this.column.props[prop]) : null;
},
updateStickyPosition() {
if (this.columnProp('frozen')) {
let align = this.columnProp('alignFrozen');
if (align === 'right') {
let right = 0;
let next = this.$el.nextElementSibling;
if (next) {
right = DomHandler.getOuterWidth(next) + parseFloat(next.style.right);
}
this.styleObject.right = right + 'px';
}
else {
let left = 0;
let prev = this.$el.previousElementSibling;
if (prev) {
left = DomHandler.getOuterWidth(prev) + parseFloat(prev.style.left);
}
this.styleObject.left = left + 'px';
}
}
},
resolveFieldData(rowData, field) {
return ObjectUtils.resolveFieldData(rowData, field);
},
toggleCheckbox() {
this.$emit('checkbox-toggle');
},
onCheckboxFocus() {
this.checkboxFocused = true;
},
onCheckboxBlur() {
this.checkboxFocused = false;
}
},
computed: {
containerClass() {
return [this.columnProp('bodyClass'), {
'p-frozen-column': this.columnProp('frozen')
}];
},
containerStyle() {
let bodyStyle = this.columnProp('bodyStyle');
let columnStyle = this.columnProp('style');
return this.columnProp('frozen') ? [columnStyle, bodyStyle, this.styleObject]: [columnStyle, bodyStyle];
},
togglerStyle() {
return {
marginLeft: this.level * this.indentation + 'rem',
visibility: this.leaf ? 'hidden' : 'visible'
};
},
togglerIcon() {
return ['p-treetable-toggler-icon pi', {'pi-chevron-right': !this.expanded, 'pi-chevron-down': this.expanded}];
},
checkboxSelectionMode() {
return this.selectionMode === 'checkbox';
},
checkboxClass() {
return ['p-checkbox-box', {'p-highlight': this.checked, 'p-focus': this.checkboxFocused, 'p-indeterminate': this.partialChecked}];
},
checkboxIcon() {
return ['p-checkbox-icon', {'pi pi-check': this.checked, 'pi pi-minus': this.partialChecked}];
}
},
directives: {
'ripple': Ripple
}
}
</script>

View File

@ -0,0 +1,74 @@
<template>
<td :style="containerStyle" :class="containerClass">
<component :is="column.children.footer" :column="col" v-if="column.children && column.children.footer" />
{{columnProp('footer')}}
</td>
</template>
<script>
import {DomHandler} from 'primevue/utils';
export default {
name: 'FooterCell',
props: {
column: {
type: Object,
default: null
}
},
data() {
return {
styleObject: {}
}
},
mounted() {
if (this.columnProp('frozen')) {
this.updateStickyPosition();
}
},
updated() {
if (this.columnProp('frozen')) {
this.updateStickyPosition();
}
},
methods: {
columnProp(prop) {
return this.column.props ? ((this.column.type.props[prop].type === Boolean && this.column.props[prop] === '') ? true : this.column.props[prop]) : null;
},
updateStickyPosition() {
if (this.columnProp('frozen')) {
let align = this.columnProp('alignFrozen');
if (align === 'right') {
let right = 0;
let next = this.$el.nextElementSibling;
if (next) {
right = DomHandler.getOuterWidth(next) + parseFloat(next.style.right);
}
this.styleObject.right = right + 'px';
}
else {
let left = 0;
let prev = this.$el.previousElementSibling;
if (prev) {
left = DomHandler.getOuterWidth(prev) + parseFloat(prev.style.left);
}
this.styleObject.left = left + 'px';
}
}
}
},
computed: {
containerClass() {
return [this.columnProp('footerClass'), {
'p-frozen-column': this.columnProp('frozen')
}];
},
containerStyle() {
let bodyStyle = this.columnProp('footerStyle');
let columnStyle = this.columnProp('style');
return this.columnProp('frozen') ? [columnStyle, bodyStyle, this.styleObject]: [columnStyle, bodyStyle];
}
}
}
</script>

View File

@ -0,0 +1,170 @@
<template>
<th :style="[containerStyle]" :class="containerClass" @click="onClick" @keydown="onKeyDown"
:tabindex="columnProp('sortable') ? '0' : null" :aria-sort="ariaSort">
<span class="p-column-resizer" @mousedown="onResizeStart" v-if="resizableColumns"></span>
<component :is="column.children.header" :column="column" v-if="column.children && column.children.header" />
<span class="p-column-title" v-if="columnProp('header')">{{columnProp('header')}}</span>
<span v-if="columnProp('sortable')" :class="sortableColumnIcon"></span>
<span v-if="isMultiSorted()" class="p-sortable-column-badge">{{getMultiSortMetaIndex() + 1}}</span>
</th>
</template>
<script>
import {DomHandler} from 'primevue/utils';
export default {
name: 'HeaderCell',
emits: ['column-click','column-resizestart'],
props: {
column: {
type: Object,
default: null
},
resizableColumns: {
type: Boolean,
default: false
},
sortField: {
type: [String, Function],
default: null
},
sortOrder: {
type: Number,
default: null
},
multiSortMeta: {
type: Array,
default: null
},
sortMode: {
type: String,
default: 'single'
}
},
data() {
return {
styleObject: {}
}
},
mounted() {
if (this.columnProp('frozen')) {
this.updateStickyPosition();
}
},
updated() {
if (this.columnProp('frozen')) {
this.updateStickyPosition();
}
},
methods: {
columnProp(prop) {
return this.column.props ? ((this.column.type.props[prop].type === Boolean && this.column.props[prop] === '') ? true : this.column.props[prop]) : null;
},
updateStickyPosition() {
if (this.columnProp('frozen')) {
let align = this.columnProp('alignFrozen');
if (align === 'right') {
let right = 0;
let next = this.$el.nextElementSibling;
if (next) {
right = DomHandler.getOuterWidth(next) + parseFloat(next.style.right);
}
this.styleObject.right = right + 'px';
}
else {
let left = 0;
let prev = this.$el.previousElementSibling;
if (prev) {
left = DomHandler.getOuterWidth(prev) + parseFloat(prev.style.left);
}
this.styleObject.left = left + 'px';
}
}
},
onClick(event) {
this.$emit('column-click', {originalEvent: event, column: this.column});
},
onKeyDown(event) {
if (event.which === 13 && event.currentTarget.nodeName === 'TH' && DomHandler.hasClass(event.currentTarget, 'p-sortable-column')) {
this.$emit('column-click', {originalEvent: event, column: this.column});
}
},
onResizeStart(event) {
this.$emit('column-resizestart', event);
},
getMultiSortMetaIndex() {
let index = -1;
for (let i = 0; i < this.multiSortMeta.length; i++) {
let meta = this.multiSortMeta[i];
if (meta.field === this.columnProp('field') || meta.field === this.columnProp('sortField')) {
index = i;
break;
}
}
return index;
},
isMultiSorted() {
return this.columnProp('sortable') && this.getMultiSortMetaIndex() > -1
},
isColumnSorted() {
return this.sortMode === 'single' ? (this.sortField && (this.sortField === this.columnProp('field') || this.sortField === this.columnProp('sortField'))) : this.isMultiSorted();
}
},
computed: {
containerClass() {
return [this.columnProp('headerClass'), {
'p-sortable-column': this.columnProp('sortable'),
'p-resizable-column': this.resizableColumns,
'p-highlight': this.isColumnSorted(),
'p-frozen-column': this.columnProp('frozen')
}];
},
containerStyle() {
let headerStyle = this.columnProp('headerStyle');
let columnStyle = this.columnProp('style');
return this.columnProp('frozen') ? [columnStyle, headerStyle, this.styleObject]: [columnStyle, headerStyle];
},
sortableColumnIcon() {
let sorted = false;
let sortOrder = null;
if (this.sortMode === 'single') {
sorted = this.sortField && (this.sortField === this.columnProp('field') || this.sortField === this.columnProp('sortField'));
sortOrder = sorted ? this.sortOrder: 0;
}
else if (this.sortMode === 'multiple') {
let metaIndex = this.getMultiSortMetaIndex();
if (metaIndex > -1) {
sorted = true;
sortOrder = this.multiSortMeta[metaIndex].order;
}
}
return [
'p-sortable-column-icon pi pi-fw', {
'pi-sort-alt': !sorted,
'pi-sort-amount-up-alt': sorted && sortOrder > 0,
'pi-sort-amount-down': sorted && sortOrder < 0
}
];
},
ariaSort() {
if (this.columnProp('sortable')) {
const sortIcon = this.sortableColumnIcon;
if (sortIcon[1]['pi-sort-amount-down'])
return 'descending';
else if (sortIcon[1]['pi-sort-amount-up-alt'])
return 'ascending';
else
return 'none';
}
else {
return null;
}
},
}
}
</script>

View File

@ -36,6 +36,9 @@ interface TreeTableProps {
columnResizeMode?: string;
indentation?: number;
showGridlines?: boolean;
scrollable?: boolean;
scrollHeight?: string;
scrollDirection?: string;
}
declare class TreeTable {

View File

@ -22,14 +22,9 @@
<thead class="p-treetable-thead">
<tr>
<template v-for="(col,i) of columns" :key="columnProp(col, 'columnKey')||columnProp(col, 'field')||i">
<th v-if="!columnProp(col, 'hidden')" :style="[columnProp(col, 'style'),columnProp(col, 'headerStyle')]" :class="getColumnHeaderClass(col)" @click="onColumnHeaderClick($event, col)"
:tabindex="columnProp(col, 'sortable') ? '0' : null" :aria-sort="getAriaSort(col)" @keydown="onColumnKeyDown($event, col)">
<span class="p-column-resizer" @mousedown="onColumnResizeStart" v-if="resizableColumns"></span>
<component :is="col.children.header" :column="col" v-if="col.children && col.children.header" />
<span class="p-column-title" v-if="columnProp(col, 'header')">{{columnProp(col, 'header')}}</span>
<span v-if="columnProp(col, 'sortable')" :class="getSortableColumnIcon(col)"></span>
<span v-if="isMultiSorted(col)" class="p-sortable-column-badge">{{getMultiSortMetaIndex(col) + 1}}</span>
</th>
<TTHeaderCell v-if="!columnProp(col, 'hidden')" :column="col" :resizableColumns="resizableColumns"
:sortField="d_sortField" :sortOrder="d_sortOrder" :multiSortMeta="d_multiSortMeta" :sortMode="sortMode"
@column-click="onColumnHeaderClick" @column-resizestart="onColumnResizeStart"></TTHeaderCell>
</template>
</tr>
<tr v-if="hasColumnFilter()">
@ -55,10 +50,7 @@
<tfoot class="p-treetable-tfoot" v-if="hasFooter">
<tr>
<template v-for="(col,i) of columns" :key="columnProp(col, 'columnKey')||columnProp(col, 'field')||i">
<td v-if="!columnProp(col, 'hidden')" :style="[columnProp(col, 'style'),columnProp(col, 'footerStyle')]" :class="getColumnFooterClass(col)">
<component :is="col.children.footer" :column="col" v-if="col.children && col.children.footer" />
{{columnProp(col, 'footer')}}
</td>
<TTFooterCell v-if="!columnProp(col, 'hidden')" :column="col"></TTFooterCell>
</template>
</tr>
</tfoot>
@ -84,6 +76,8 @@
import {ObjectUtils,DomHandler} from 'primevue/utils';
import {FilterService} from 'primevue/api';
import TreeTableRow from './TreeTableRow.vue';
import HeaderCell from './HeaderCell.vue';
import FooterCell from './FooterCell.vue';
import Paginator from 'primevue/paginator';
export default {
@ -392,72 +386,15 @@ export default {
this.d_first = 0;
this.$emit('update:first', this.d_first);
},
isMultiSorted(column) {
return this.columnProp(column, 'sortable') && this.getMultiSortMetaIndex(column) > -1
},
isColumnSorted(column) {
if (this.columnProp(column, 'sortable')) {
return this.sortMode === 'single' ? (this.d_sortField === (this.columnProp(column, 'field') || this.columnProp(column, 'sortField'))) : this.getMultiSortMetaIndex(column) > -1;
}
return false;
},
getColumnHeaderClass(column) {
return [this.columnProp(column, 'headerClass'), {
'p-sortable-column': this.columnProp(column, 'sortable'),
'p-resizable-column': this.resizableColumns,
'p-highlight': this.isColumnSorted(column),
'p-frozen-column': this.columnProp(column, 'frozen')
}];
},
getFilterColumnHeaderClass(column) {
return ['p-filter-column', this.columnProp(column, 'filterHeaderClass'), {
'p-frozen-column': this.columnProp(column, 'frozen')
}];
},
getColumnFooterClass(column) {
return [this.columnProp(column, 'footerClass'), {
'p-frozen-column': this.columnProp(column, 'frozen')
}];
},
getSortableColumnIcon(column) {
let sorted = false;
let sortOrder = null;
onColumnHeaderClick(e) {
let event = e.originalEvent;
let column = e.column;
if (this.sortMode === 'single') {
sorted = this.d_sortField === (this.columnProp(column, 'field')|| this.columnProp(column, 'sortField'));
sortOrder = sorted ? this.d_sortOrder: 0;
}
else if (this.sortMode === 'multiple') {
let metaIndex = this.getMultiSortMetaIndex(column);
if (metaIndex > -1) {
sorted = true;
sortOrder = this.d_multiSortMeta[metaIndex].order;
}
}
return [
'p-sortable-column-icon pi pi-fw', {
'pi-sort-alt': !sorted,
'pi-sort-amount-up-alt': sorted && sortOrder > 0,
'pi-sort-amount-down': sorted && sortOrder < 0
}
];
},
getMultiSortMetaIndex(column) {
let index = -1;
for (let i = 0; i < this.d_multiSortMeta.length; i++) {
let meta = this.d_multiSortMeta[i];
if (meta.field === (this.columnProp(column, 'field')|| this.columnProp(column, 'sortField'))) {
index = i;
break;
}
}
return index;
},
onColumnHeaderClick(event, column) {
if (this.columnProp(column, 'sortable')) {
const targetNode = event.target;
const columnField = this.columnProp(column, 'sortField') || this.columnProp(column, 'field');
@ -786,20 +723,6 @@ export default {
this.onColumnHeaderClick(event, col);
}
},
getAriaSort(column) {
if (this.columnProp(column, 'sortable')) {
const sortIcon = this.getSortableColumnIcon(column);
if (sortIcon[1]['pi-sort-amount-down'])
return 'descending';
else if (sortIcon[1]['pi-sort-amount-up-alt'])
return 'ascending';
else
return 'none';
}
else {
return null;
}
},
hasColumnFilter() {
if (this.columns) {
for (let col of this.columns) {
@ -933,6 +856,8 @@ export default {
components: {
'TTRow': TreeTableRow,
'TTPaginator': Paginator,
'TTHeaderCell': HeaderCell,
'TTFooterCell': FooterCell
}
}
</script>

View File

@ -1,21 +1,10 @@
<template>
<tr :class="containerClass" @click="onClick" @keydown="onKeyDown" @touchend="onTouchEnd" :style="node.style" tabindex="0">
<template v-for="(col,i) of columns" :key="columnProp(col, 'columnKey')||columnProp(col, 'field')||i">
<td v-if="!columnProp(col, 'hidden')" :style="[columnProp(col, 'style'),columnProp(col, 'bodyStyle')]" :class="getColumnBodyClass(col)">
<button type="button" class="p-treetable-toggler p-link" @click="toggle" v-if="columnProp(col, '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 && columnProp(col, 'expander')" role="checkbox" :aria-checked="checked">
<div class="p-hidden-accessible">
<input type="checkbox" @focus="onCheckboxFocus" @blur="onCheckboxBlur" />
</div>
<div ref="checkboxEl" :class="checkboxClass">
<span :class="checkboxIcon"></span>
</div>
</div>
<component :is="col.children.body" :node="node" :column="col" v-if="col.children && col.children.body" />
<template v-else><span>{{resolveFieldData(node.data, columnProp(col, 'field'))}}</span></template>
</td>
<TTBodyCell v-if="!columnProp(col, 'hidden')" :column="col" :node="node"
:level="level" :leaf="leaf" :indentation="indentation" :expanded="expanded" :selectionMode="selectionMode"
:checked="checked" :partialChecked="partialChecked"
@node-toggle="$emit('node-toggle', $event)" @checkbox-toggle="toggleCheckbox"></TTBodyCell>
</template>
</tr>
<template v-if="expanded && node.children && node.children.length">
@ -26,9 +15,8 @@
</template>
<script>
import {ObjectUtils} from 'primevue/utils';
import {DomHandler} from 'primevue/utils';
import Ripple from 'primevue/ripple';
import BodyCell from './BodyCell.vue';
export default {
name: 'TreeTableRow',
@ -67,19 +55,11 @@ export default {
default: 1
}
},
data() {
return {
checkboxFocused: false
}
},
nodeTouched: false,
methods: {
columnProp(col, prop) {
return col.props ? ((col.type.props[prop].type === Boolean && col.props[prop] === '') ? true : col.props[prop]) : null;
},
resolveFieldData(rowData, field) {
return ObjectUtils.resolveFieldData(rowData, field);
},
toggle() {
this.$emit('node-toggle', this.node);
},
@ -207,12 +187,6 @@ export default {
selectionKeys: _selectionKeys
});
},
onCheckboxFocus() {
this.checkboxFocused = true;
},
onCheckboxBlur() {
this.checkboxFocused = false;
},
onCheckboxChange(event) {
let check = event.check;
let _selectionKeys = {...event.selectionKeys};
@ -245,12 +219,7 @@ export default {
check: event.check,
selectionKeys: _selectionKeys
});
},
getColumnBodyClass(column) {
return [this.columnProp(column, 'bodyClass'), {
'p-frozen-column': this.columnProp(column, 'frozen')
}];
},
}
},
computed: {
containerClass() {
@ -270,27 +239,9 @@ export default {
selected() {
return (this.selectionMode && this.selectionKeys) ? this.selectionKeys[this.node.key] === true : false;
},
togglerIcon() {
return ['p-treetable-toggler-icon pi', {'pi-chevron-right': !this.expanded, 'pi-chevron-down': this.expanded}];
},
togglerStyle() {
return {
marginLeft: this.level * this.indentation + 'rem',
visibility: this.leaf ? 'hidden' : 'visible'
};
},
childLevel() {
return this.level + 1;
},
checkboxSelectionMode() {
return this.selectionMode === 'checkbox';
},
checkboxClass() {
return ['p-checkbox-box', {'p-highlight': this.checked, 'p-focus': this.checkboxFocused, 'p-indeterminate': this.partialChecked}];
},
checkboxIcon() {
return ['p-checkbox-icon', {'pi pi-check': this.checked, 'pi pi-minus': this.partialChecked}];
},
checked() {
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].checked: false;
},
@ -298,8 +249,8 @@ export default {
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].partialChecked: false;
}
},
directives: {
'ripple': Ripple
components: {
'TTBodyCell': BodyCell
}
}
</script>

View File

@ -1047,6 +1047,84 @@ export default {
}
}
</code></pre>
<h5>Scrolling</h5>
<p>TreeTable supports both horizontal and vertical scrolling with support for frozen columns. Scrollable TreeTable is enabled using <i>scrollable</i> property and <i>scrollHeight</i> to define the viewport height.</p>
<pre v-code><code><template v-pre>
&lt;TreeTable :value="nodes" style="margin-bottom: 2rem" :scrollable="true" scrollHeight="400px"&gt;
&lt;Column field="name" header="Name" :expander="true" style="min-width:200px"&gt;&lt;/Column&gt;
&lt;Column field="size" header="Size" style="min-width:200px"&gt;&lt;/Column&gt;
&lt;Column field="type" header="Type" style="min-width:200px"&gt;&lt;/Column&gt;
&lt;/TreeTable&gt;
</template>
</code></pre>
<h5>Column Widths of a Scrollable TreeTable</h5>
<p>Scrollable treetable uses flex layout so there are a couple of rules to consider when adjusting the widths of columns.</p>
<ul>
<li>Use <i>min-width</i> in vertical scrolling only so that when there is enough space columns may grow and for smaller screens a horizontal scrollbar is displayed to provide responsive design.</li>
<li>When horizontal scrolling is enabled, prefer <i>width</i> instead of <i>min-width</i>.</li>
<li>In vertical scrolling only, use <i>flex</i> to disable grow and shrink while defining a initial width. When horizontal scrolling is enabled, this is not required as columns do not grow or shrink in horizontal scrolling.</li>
</ul>
<pre v-code><code><template v-pre>
&lt;Column field="vin" header="Vin" style="flex: 0 0 4rem"&gt;&lt;/Column&gt;
</template>
</code></pre>
<h6>Flex Scroll</h6>
<p>In cases where viewport should adjust itself according to the table parent's height instead of a fixed viewport height, set scrollHeight option as flex. In example below, table is inside a Dialog where viewport size dynamically responds to the dialog size changes such as maximizing.
FlexScroll can also be used for cases where scrollable viewport should be responsive with respect to the window size for full page scroll.</p>
<pre v-code><code><template v-pre>
&lt;Button label="Show" icon="pi pi-external-link" @click="openDialog" /&gt;
&lt;Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '50vw'}" :maximizable="true" :modal="true" :contentStyle="{height: '300px'}"&gt;
&lt;TreeTable :value="nodes" :scrollable="true" scrollHeight="flex"&gt;
&lt;Column field="name" header="Name" :expander="true" style="min-width:200px"&gt;&lt;/Column&gt;
&lt;Column field="size" header="Size" style="min-width:200px"&gt;&lt;/Column&gt;
&lt;Column field="type" header="Type" style="min-width:200px"&gt;&lt;/Column&gt;
&lt;/TreeTable&gt;
&lt;template #footer&gt;
&lt;Button label="Yes" icon="pi pi-check" @click="closeDialog" /&gt;
&lt;Button label="No" icon="pi pi-times" @click="closeDialog" class="p-button-secondary"/&gt;
&lt;/template&gt;
&lt;/Dialog&gt;
</template>
</code></pre>
<h6>Horizontal Scrolling</h6>
<p>For horizontal scrolling, it is required to set <i>scrollDirection</i> to "horizontal" and give fixed widths to columns.</p>
<pre v-code><code><template v-pre>
&lt;TreeTable :value="nodes" :scrollable="true" scrollDirection="horizontal"&gt;
&lt;Column field="name" header="Name" :expander="true" style="width:200px"&gt;&lt;/Column&gt;
&lt;Column field="size" header="Size" style="width:200px"&gt;&lt;/Column&gt;
&lt;Column field="type" header="Type" style="width:200px"&gt;&lt;/Column&gt;
&lt;/TreeTable&gt;
</template>
</code></pre>
<h6>Horizontal and Vertical Scrolling</h6>
<p>Set <i>scrollDirection</i> to "both" and give fixed widths to columns to scroll both ways.</p>
<pre v-code><code><template v-pre>
&lt;TreeTable :value="customers" :scrollable="true" scrollHeight="400px" scrollDirection="both"&gt;
&lt;Column field="name" header="Name" :expander="true" style="width:200px"&gt;&lt;/Column&gt;
&lt;Column field="size" header="Size" style="width:200px"&gt;&lt;/Column&gt;
&lt;Column field="type" header="Type" style="width:200px"&gt;&lt;/Column&gt;
&lt;/TreeTable&gt;
</template>
</code></pre>
<h6>Frozen Columns</h6>
<p>Certain columns can be frozen by using the <i>frozen</i> property of the column component. In addition <i>alignFrozen</i> is available to define whether the column should
be fixed on the left or right.</p>
<pre v-code><code><template v-pre>
&lt;TreeTable :value="customers" :scrollable="true" scrollHeight="400px" scrollDirection="both"&gt;
&lt;Column field="name" header="Name" :expander="true" style="width:200px" frozen&gt;&lt;/Column&gt;
&lt;Column field="size" header="Size" style="width:200px"&gt;&lt;/Column&gt;
&lt;Column field="type" header="Type" style="width:200px" frozen alignFrozen="right"&gt;&lt;/Column&gt;
&lt;/TreeTable&gt;
</template>
</code></pre>
<h5>Lazy</h5>
@ -1457,6 +1535,24 @@ export default {
<td>boolean</td>
<td>false</td>
<td>Whether to show grid lines between cells.</td>
</tr>
<tr>
<td>scrollable</td>
<td>boolean</td>
<td>false</td>
<td>When specified, enables horizontal and/or vertical scrolling.</td>
</tr>
<tr>
<td>scrollDirection</td>
<td>string</td>
<td>vertical</td>
<td>Orientation of the scrolling, options are "vertical", "horizontal" and "both".</td>
</tr>
<tr>
<td>scrollHeight</td>
<td>string</td>
<td>null</td>
<td>Height of the scroll viewport in fixed pixels or the "flex" keyword for a dynamic size.</td>
</tr>
</tbody>
</table>

View File

@ -80,7 +80,7 @@
{{node.children ? node.children.length : 0}}
</template>
</Column>
<Column header="Options" style="width:200px" alignFrozen="right" :frozen="balanceFrozen">
<Column header="Options" style="width:200px" alignFrozen="right" :frozen="optionsFrozen">
<template #body>
<Button type="Button" icon="pi pi-check" label="Edit Item" class="p-mr-2" style="width:100%"></Button>
</template>
@ -109,35 +109,81 @@ export default {
<template>
<div>
<div class="card">
<TreeTable :value="nodes" class="p-treetable-sm" style="margin-bottom: 2rem">
<template #header>
Small Table
</template>
<Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
<h5>Vertical</h5>
<TreeTable :value="nodes" style="margin-bottom: 2rem" :scrollable="true" scrollHeight="400px">
<Column field="name" header="Name" :expander="true" style="min-width:200px"></Column>
<Column field="size" header="Size" style="min-width:200px"></Column>
<Column field="type" header="Type" style="min-width:200px"></Column>
</TreeTable>
</div>
<div class="card">
<TreeTable :value="nodes" style="margin-bottom: 2rem">
<template #header>
Normal Table
</template>
<Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
<h5>Flexible Scroll</h5>
<p>Flex scroll feature makes the scrollable viewport section dynamic insteaf of a fixed value so that it can grow or shrink relative to the parent size of the table.
Click the button below to display a maximizable Dialog where data viewport adjusts itself according to the size changes.</p>
<Button label="Show" icon="pi pi-external-link" @click="openDialog" />
</div>
<Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '75vw'}" :maximizable="true" :modal="true" :contentStyle="{height: '300px'}">
<TreeTable :value="nodes" :scrollable="true" scrollHeight="flex">
<Column field="name" header="Name" :expander="true" style="min-width:200px"></Column>
<Column field="size" header="Size" style="min-width:200px"></Column>
<Column field="type" header="Type" style="min-width:200px"></Column>
</TreeTable>
<template #footer>
<Button label="Ok" icon="pi pi-check" @click="closeDialog" />
</template>
</Dialog>
<div class="card">
<h5>Horizontal and Vertical with Footer</h5>
<TreeTable :value="nodes" :scrollable="true" scrollHeight="400px" scrollDirection="both">
<Column field="name" header="Name" footer="Name" :expander="true" style="width:300px"></Column>
<Column header="Key" footer="Key" style="width:300px">
<template #body="{node}">
{{node.key}}
</template>
</Column>
<Column field="size" header="Size" footer="Size" style="width:300px"></Column>
<Column field="type" header="Type" footer="Type" style="width:300px"></Column>
<Column header="Children" footer="Children" style="width:300px">
<template #body="{node}">
{{node.children ? node.children.length : 0}}
</template>
</Column>
<Column header="Options" footer="Options" style="width:300px">
<template #body>
<Button type="Button" icon="pi pi-check" label="Edit" class="p-mr-2"></Button>
<Button type="Button" icon="pi pi-check" label="Delete" class="p-button-warning"></Button>
</template>
</Column>
</TreeTable>
</div>
<div class="card">
<TreeTable :value="nodes" class="p-treetable-lg" >
<template #header>
Large Table
</template>
<Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
<h5>Frozen Columns</h5>
<ToggleButton v-model="optionsFrozen" onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Unfreeze Options" offLabel="Freeze Options" style="width: 12rem" />
<TreeTable :value="nodes" :scrollable="true" scrollHeight="400px" scrollDirection="both" class="p-mt-3">
<Column field="name" header="Name" :expander="true" style="width:300px" frozen></Column>
<Column header="Key" style="width:300px">
<template #body="{node}">
{{node.key}}
</template>
</Column>
<Column field="size" header="Size" style="width:300px"></Column>
<Column field="type" header="Type" style="width:300px"></Column>
<Column header="Children" style="width:300px">
<template #body="{node}">
{{node.children ? node.children.length : 0}}
</template>
</Column>
<Column header="Options" style="width:200px" alignFrozen="right" :frozen="optionsFrozen">
<template #body>
<Button type="Button" icon="pi pi-check" label="Edit Item" class="p-mr-2" style="width:100%"></Button>
</template>
</Column>
</TreeTable>
</div>
</div>
@ -149,7 +195,9 @@ import NodeService from './service/NodeService';
export default {
data() {
return {
nodes: null
nodes: null,
dialogVisible: false,
optionsFrozen: false
}
},
nodeService: null,
@ -158,6 +206,14 @@ export default {
},
mounted() {
this.nodeService.getTreeTableNodes().then(data => this.nodes = data);
},
methods: {
openDialog() {
this.dialogVisible = true;
},
closeDialog() {
this.dialogVisible = false;
}
}
}
<\\/script>
@ -169,35 +225,81 @@ export default {
<template>
<div>
<div class="card">
<TreeTable :value="nodes" class="p-treetable-sm" style="margin-bottom: 2rem">
<template #header>
Small Table
</template>
<Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
<h5>Vertical</h5>
<TreeTable :value="nodes" style="margin-bottom: 2rem" :scrollable="true" scrollHeight="400px">
<Column field="name" header="Name" :expander="true" style="min-width:200px"></Column>
<Column field="size" header="Size" style="min-width:200px"></Column>
<Column field="type" header="Type" style="min-width:200px"></Column>
</TreeTable>
</div>
<div class="card">
<TreeTable :value="nodes" style="margin-bottom: 2rem">
<template #header>
Normal Table
</template>
<Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
<h5>Flexible Scroll</h5>
<p>Flex scroll feature makes the scrollable viewport section dynamic insteaf of a fixed value so that it can grow or shrink relative to the parent size of the table.
Click the button below to display a maximizable Dialog where data viewport adjusts itself according to the size changes.</p>
<Button label="Show" icon="pi pi-external-link" @click="openDialog" />
</div>
<Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '75vw'}" :maximizable="true" :modal="true" :contentStyle="{height: '300px'}">
<TreeTable :value="nodes" :scrollable="true" scrollHeight="flex">
<Column field="name" header="Name" :expander="true" style="min-width:200px"></Column>
<Column field="size" header="Size" style="min-width:200px"></Column>
<Column field="type" header="Type" style="min-width:200px"></Column>
</TreeTable>
<template #footer>
<Button label="Ok" icon="pi pi-check" @click="closeDialog" />
</template>
</Dialog>
<div class="card">
<h5>Horizontal and Vertical with Footer</h5>
<TreeTable :value="nodes" :scrollable="true" scrollHeight="400px" scrollDirection="both">
<Column field="name" header="Name" footer="Name" :expander="true" style="width:300px"></Column>
<Column header="Key" footer="Key" style="width:300px">
<template #body="{node}">
{{node.key}}
</template>
</Column>
<Column field="size" header="Size" footer="Size" style="width:300px"></Column>
<Column field="type" header="Type" footer="Type" style="width:300px"></Column>
<Column header="Children" footer="Children" style="width:300px">
<template #body="{node}">
{{node.children ? node.children.length : 0}}
</template>
</Column>
<Column header="Options" footer="Options" style="width:300px">
<template #body>
<Button type="Button" icon="pi pi-check" label="Edit" class="p-mr-2"></Button>
<Button type="Button" icon="pi pi-check" label="Delete" class="p-button-warning"></Button>
</template>
</Column>
</TreeTable>
</div>
<div class="card">
<TreeTable :value="nodes" class="p-treetable-lg" >
<template #header>
Large Table
</template>
<Column field="name" header="Name" :expander="true"></Column>
<Column field="size" header="Size"></Column>
<Column field="type" header="Type"></Column>
<h5>Frozen Columns</h5>
<ToggleButton v-model="optionsFrozen" onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Unfreeze Options" offLabel="Freeze Options" style="width: 12rem" />
<TreeTable :value="nodes" :scrollable="true" scrollHeight="400px" scrollDirection="both" class="p-mt-3">
<Column field="name" header="Name" :expander="true" style="width:300px" frozen></Column>
<Column header="Key" style="width:300px">
<template #body="{node}">
{{node.key}}
</template>
</Column>
<Column field="size" header="Size" style="width:300px"></Column>
<Column field="type" header="Type" style="width:300px"></Column>
<Column header="Children" style="width:300px">
<template #body="{node}">
{{node.children ? node.children.length : 0}}
</template>
</Column>
<Column header="Options" style="width:200px" alignFrozen="right" :frozen="optionsFrozen">
<template #body>
<Button type="Button" icon="pi pi-check" label="Edit Item" class="p-mr-2" style="width:100%"></Button>
</template>
</Column>
</TreeTable>
</div>
</div>
@ -215,8 +317,17 @@ export default {
const nodes = ref();
const nodeService = ref(new NodeService());
const dialogVisible = ref(false);
const optionsFrozen = ref(false);
return { nodes, nodeService }
const openDialog = () => {
dialogVisible.value = true;
};
const closeDialog = () => {
dialogVisible.value = false;
};
return { nodes, openDialog, closeDialog, dialogVisible, optionsFrozen }
}
}
<\\/script>
@ -242,3 +353,9 @@ export default {
}
}
</script>
<style lang="scss" scoped>
::v-deep(.p-treetable-scrollable .p-frozen-column) {
font-weight: bold;
}
</style>

View File

@ -166,7 +166,7 @@ export default {
})
const nodes = ref();
const nodeService = ref(new NodeService());
const nodeService = new NodeService();
return { nodes, nodeService }
}