Initiated VirtualScroll Table

pull/104/head
cagataycivici 2019-11-19 14:54:24 +03:00
parent 2889aeae5b
commit 24b3ac7ae0
4 changed files with 205 additions and 34 deletions

View File

@ -61,7 +61,7 @@
@row-edit-init="onRowEditInit($event)" @row-edit-save="onRowEditSave($event)" @row-edit-cancel="onRowEditCancel($event)"/> @row-edit-init="onRowEditInit($event)" @row-edit-save="onRowEditSave($event)" @row-edit-cancel="onRowEditCancel($event)"/>
</template> </template>
<template #frozenbody> <template #frozenbody>
<DTTableBody :value="frozenValue" :columns="frozenColumns" :dataKey="dataKey" :selection="selection" :selectionKeys="d_selectionKeys" :selectionMode="selectionMode" <DTTableBody v-if="frozenValue" :value="frozenValue" :columns="frozenColumns" :dataKey="dataKey" :selection="selection" :selectionKeys="d_selectionKeys" :selectionMode="selectionMode"
:rowGroupMode="rowGroupMode" :groupRowsBy="groupRowsBy" :expandableRowGroups="expandableRowGroups" :rowClass="rowClass" :editMode="editMode" :compareSelectionBy="compareSelectionBy" :rowGroupMode="rowGroupMode" :groupRowsBy="groupRowsBy" :expandableRowGroups="expandableRowGroups" :rowClass="rowClass" :editMode="editMode" :compareSelectionBy="compareSelectionBy"
:expandedRowIcon="expandedRowIcon" :collapsedRowIcon="collapsedRowIcon" :expandedRows="expandedRows" :expandedRowKeys="d_expandedRowKeys" :expandedRowGroups="expandedRowGroups" :expandedRowIcon="expandedRowIcon" :collapsedRowIcon="collapsedRowIcon" :expandedRows="expandedRows" :expandedRowKeys="d_expandedRowKeys" :expandedRowGroups="expandedRowGroups"
:editingRows="editingRows" :editingRowKeys="d_editingRowKeys" :templates="$scopedSlots" :editingRows="editingRows" :editingRowKeys="d_editingRowKeys" :templates="$scopedSlots"
@ -75,7 +75,8 @@
<DTTableFooter :columnGroup="frozenFooterColumnGroup" :columns="frozenColumns" /> <DTTableFooter :columnGroup="frozenFooterColumnGroup" :columns="frozenColumns" />
</template> </template>
</DTScrollableView> </DTScrollableView>
<DTScrollableView :scrollHeight="scrollHeight" :columns="scrollableColumns" :frozenWidth="frozenWidth"> <DTScrollableView :scrollHeight="scrollHeight" :columns="scrollableColumns" :frozenWidth="frozenWidth" :rows="rows"
:virtualScroll="virtualScroll" :virtualRowHeight="virtualRowHeight" :totalRecords="totalRecordsLength" @virtual-scroll="onVirtualScroll">
<template #header> <template #header>
<DTTableHeader :columnGroup="headerColumnGroup" :columns="scrollableColumns" :rowGroupMode="rowGroupMode" <DTTableHeader :columnGroup="headerColumnGroup" :columns="scrollableColumns" :rowGroupMode="rowGroupMode"
:groupRowsBy="groupRowsBy" :resizableColumns="resizableColumns" :allRowsSelected="allRowsSelected" :empty="empty" :groupRowsBy="groupRowsBy" :resizableColumns="resizableColumns" :allRowsSelected="allRowsSelected" :empty="empty"
@ -96,7 +97,7 @@
@row-edit-init="onRowEditInit($event)" @row-edit-save="onRowEditSave($event)" @row-edit-cancel="onRowEditCancel($event)"/> @row-edit-init="onRowEditInit($event)" @row-edit-save="onRowEditSave($event)" @row-edit-cancel="onRowEditCancel($event)"/>
</template> </template>
<template #frozenbody> <template #frozenbody>
<DTTableBody :value="frozenValue" :columns="scrollableColumns" :dataKey="dataKey" :selection="selection" :selectionKeys="d_selectionKeys" :selectionMode="selectionMode" <DTTableBody v-if="frozenValue" :value="frozenValue" :columns="scrollableColumns" :dataKey="dataKey" :selection="selection" :selectionKeys="d_selectionKeys" :selectionMode="selectionMode"
:rowGroupMode="rowGroupMode" :groupRowsBy="groupRowsBy" :expandableRowGroups="expandableRowGroups" :rowClass="rowClass" :editMode="editMode" :compareSelectionBy="compareSelectionBy" :rowGroupMode="rowGroupMode" :groupRowsBy="groupRowsBy" :expandableRowGroups="expandableRowGroups" :rowClass="rowClass" :editMode="editMode" :compareSelectionBy="compareSelectionBy"
:expandedRowIcon="expandedRowIcon" :collapsedRowIcon="collapsedRowIcon" :expandedRows="expandedRows" :expandedRowKeys="d_expandedRowKeys" :expandedRowGroups="expandedRowGroups" :expandedRowIcon="expandedRowIcon" :collapsedRowIcon="collapsedRowIcon" :expandedRows="expandedRows" :expandedRowKeys="d_expandedRowKeys" :expandedRowGroups="expandedRowGroups"
:editingRows="editingRows" :editingRowKeys="d_editingRowKeys" :templates="$scopedSlots" :editingRows="editingRows" :editingRowKeys="d_editingRowKeys" :templates="$scopedSlots"
@ -332,6 +333,18 @@ export default {
frozenWidth: { frozenWidth: {
type: String, type: String,
default: null default: null
},
virtualScroll: {
type: Boolean,
default: false
},
virtualRowHeight: {
type: Number,
default: null
},
virtualScrollDelay: {
type: Number,
default: 159
} }
}, },
data() { data() {
@ -365,6 +378,7 @@ export default {
columnWidthsState: null, columnWidthsState: null,
tableWidthState: null, tableWidthState: null,
columnWidthsRestored: false, columnWidthsRestored: false,
virtualScrollTimer: null,
watch: { watch: {
first(newValue) { first(newValue) {
this.d_first = newValue; this.d_first = newValue;
@ -1488,6 +1502,18 @@ export default {
_editingRows.splice(this.findIndex(event.data, this._editingRows), 1); _editingRows.splice(this.findIndex(event.data, this._editingRows), 1);
this.$emit('update:editingRows', _editingRows); this.$emit('update:editingRows', _editingRows);
this.$emit('row-edit-cancel', event); this.$emit('row-edit-cancel', event);
},
onVirtualScroll(event) {
if(this.virtualScrollTimer) {
clearTimeout(this.virtualScrollTimer);
}
this.virtualScrollTimer = setTimeout(() => {
this.$emit('virtual-scroll', {
first: (event.page - 1) * this.rows,
rows: this.rows * 2
});
}, this.virtualScrollDelay);
} }
}, },
computed: { computed: {
@ -1498,7 +1524,8 @@ export default {
'p-datatable-auto-layout': this.autoLayout, 'p-datatable-auto-layout': this.autoLayout,
'p-datatable-resizable': this.resizableColumns, 'p-datatable-resizable': this.resizableColumns,
'p-datatable-resizable-fit': this.resizableColumns && this.columnResizeMode === 'fit', 'p-datatable-resizable-fit': this.resizableColumns && this.columnResizeMode === 'fit',
'p-datatable-scrollable': this.scrollable 'p-datatable-scrollable': this.scrollable,
'p-datatable-virtual-scrollable': this.virtualScroll
} }
]; ];
}, },

View File

@ -12,12 +12,18 @@
</div> </div>
</div> </div>
<div class="p-datatable-scrollable-body" ref="scrollBody" @scroll="onBodyScroll"> <div class="p-datatable-scrollable-body" ref="scrollBody" @scroll="onBodyScroll">
<table ref="scrollTable"> <table ref="scrollTable" :class="bodyTableClass" :style="bodyTableStyle">
<colgroup class="p-datatable-scrollable-colgroup"> <colgroup class="p-datatable-scrollable-colgroup">
<col v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.headerStyle" /> <col v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.headerStyle" />
</colgroup> </colgroup>
<slot name="body"></slot> <slot name="body"></slot>
</table> </table>
<table ref="loadingTable" :style="{top:'0', display: 'none'}" class="p-datatable-scrollable-body-table p-datatable-loading-virtual-table p-datatable-virtual-table">
<colgroup class="p-datatable-scrollable-colgroup">
<col v-for="(col,i) of columns" :key="col.columnKey||col.field||i" :style="col.headerStyle" />
</colgroup>
</table>
<div class="p-datatable-virtual-scroller" ref="virtualScroller"></div>
</div> </div>
<div class="p-datatable-scrollable-footer" ref="scrollFooter"> <div class="p-datatable-scrollable-footer" ref="scrollFooter">
<div class="p-datatable-scrollable-footer-box" ref="scrollFooterBox"> <div class="p-datatable-scrollable-footer-box" ref="scrollFooterBox">
@ -52,8 +58,25 @@ export default {
scrollHeight: { scrollHeight: {
type: String, type: String,
default: null default: null
},
virtualScroll: {
type: Boolean,
default: false
},
virtualRowHeight: {
type: Number,
default: null
},
rows: {
type: Number,
default: null,
},
totalRecords: {
type: Number,
default: 0
} }
}, },
virtualScrollCallback: null,
mounted() { mounted() {
this.setScrollHeight(); this.setScrollHeight();
@ -61,10 +84,29 @@ export default {
this.alignScrollBar(); this.alignScrollBar();
else else
this.$refs.scrollBody.style.paddingBottom = DomHandler.calculateScrollbarWidth() + 'px'; this.$refs.scrollBody.style.paddingBottom = DomHandler.calculateScrollbarWidth() + 'px';
if (this.virtualScroll) {
this.$refs.virtualScroller.style.height = this.totalRecords * this.virtualRowHeight + 'px';
}
},
updated() {
if (!this.frozen) {
this.alignScrollBar();
}
if (this.virtualScrollCallback) {
this.virtualScrollCallback();
this.virtualScrollCallback = null;
}
}, },
watch: { watch: {
scrollHeight() { scrollHeight() {
this.setScrollHeight(); this.setScrollHeight();
},
totalRecords(newValue) {
if (this.virtualScroll) {
this.$refs.virtualScroller.style.height = newValue * this.virtualRowHeight + 'px';
}
} }
}, },
methods: { methods: {
@ -78,45 +120,43 @@ export default {
frozenScrollBody = DomHandler.findSingle(frozenView, '.p-datatable-scrollable-body'); frozenScrollBody = DomHandler.findSingle(frozenView, '.p-datatable-scrollable-body');
} }
if (frozenScrollBody) {
frozenScrollBody.scrollTop = this.$refs.scrollBody.scrollTop;
}
this.$refs.scrollHeaderBox.style.marginLeft = -1 * this.$refs.scrollBody.scrollLeft + 'px'; this.$refs.scrollHeaderBox.style.marginLeft = -1 * this.$refs.scrollBody.scrollLeft + 'px';
if (this.$refs.scrollFooterBox) { if (this.$refs.scrollFooterBox) {
this.$refs.scrollFooterBox.style.marginLeft = -1 * this.$refs.scrollBody.scrollLeft + 'px'; this.$refs.scrollFooterBox.style.marginLeft = -1 * this.$refs.scrollBody.scrollLeft + 'px';
} }
if (frozenScrollBody) { if (this.virtualScroll) {
frozenScrollBody.scrollTop = this.$refs.scrollBody.scrollTop; let viewport = DomHandler.getClientHeight(this.$refs.scrollBody);
} let tableHeight = DomHandler.getOuterHeight(this.$refs.scrollTable);
/* let pageHeight = this.virtualRowHeight * this.rows;
if (this.props.virtualScroll) { let virtualTableHeight = DomHandler.getOuterHeight(this.$refs.virtualScroller);
let viewport = DomHandler.getClientHeight(this.scrollBody);
let tableHeight = DomHandler.getOuterHeight(this.scrollTable);
let pageHeight = this.props.virtualRowHeight * this.props.rows;
let virtualTableHeight = DomHandler.getOuterHeight(this.virtualScroller);
let pageCount = (virtualTableHeight / pageHeight)||1; let pageCount = (virtualTableHeight / pageHeight)||1;
let scrollBodyTop = this.scrollTable.style.top||'0'; let scrollBodyTop = this.$refs.scrollTable.style.top||'0';
if(this.scrollBody.scrollTop + viewport > parseFloat(scrollBodyTop) + tableHeight || this.scrollBody.scrollTop < parseFloat(scrollBodyTop)) { if(this.$refs.scrollBody.scrollTop + viewport > parseFloat(scrollBodyTop) + tableHeight || this.$refs.scrollBody.scrollTop < parseFloat(scrollBodyTop)) {
if (this.loadingTable) { if (this.$refs.loadingTable) {
this.loadingTable.style.display = 'table'; this.$refs.loadingTable.style.display = 'table';
this.loadingTable.style.top = this.scrollBody.scrollTop + 'px'; this.$refs.loadingTable.style.top = this.$refs.scrollBody.scrollTop + 'px';
} }
let page = Math.floor((this.scrollBody.scrollTop * pageCount) / (this.scrollBody.scrollHeight)) + 1; let page = Math.floor((this.$refs.scrollBody.scrollTop * pageCount) / (this.$refs.scrollBody.scrollHeight)) + 1;
if(this.props.onVirtualScroll) { this.$emit('virtual-scroll', {
this.props.onVirtualScroll({
page: page page: page
}); });
this.virtualScrollCallback = () => { this.virtualScrollCallback = () => {
if (this.loadingTable) { if (this.$refs.loadingTable) {
this.loadingTable.style.display = 'none'; this.$refs.loadingTable.style.display = 'none';
} }
this.scrollTable.style.top = ((page - 1) * pageHeight) + 'px'; this.$refs.scrollTable.style.top = ((page - 1) * pageHeight) + 'px';
} }
} }
} }
}*/
}, },
setScrollHeight() { setScrollHeight() {
if (this.scrollHeight) { if (this.scrollHeight) {
@ -184,6 +224,12 @@ export default {
else { else {
return null; return null;
} }
},
bodyTableClass() {
return ['p-datatable-scrollable-body-table', {'p-datatable-virtual-table': this.virtualScroll}];
},
bodyTableStyle() {
return this.virtualScroll ? {top: '0'} : null;
} }
} }
} }

View File

@ -58,6 +58,21 @@ export default class DomHandler {
} }
} }
static getClientHeight(el, margin) {
if (el) {
let height = el.clientHeight;
if (margin) {
let style = getComputedStyle(el);
height += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
}
return height;
} else {
return 0;
}
}
static getViewport() { static getViewport() {
let win = window, let win = window,
d = document, d = document,

View File

@ -18,6 +18,15 @@
<Column field="color" header="Color"></Column> <Column field="color" header="Color"></Column>
</DataTable> </DataTable>
<h3>Virtual Scroll</h3>
<DataTable :value="lazyCars" :scrollable="true" scrollHeight="200px" :lazy="true" :rows="20"
:virtualScroll="true" :virtualRowHeight="30" @virtual-scroll="onVirtualScroll" :totalRecords="lazyTotalRecords">
<Column field="vin" header="Vin"></Column>
<Column field="year" header="Year"></Column>
<Column field="brand" header="Brand"></Column>
<Column field="color" header="Color"></Column>
</DataTable>
<h3>Horizontal and Vertical</h3> <h3>Horizontal and Vertical</h3>
<DataTable :value="cars" :scrollable="true" scrollHeight="200px" style="width: 600px"> <DataTable :value="cars" :scrollable="true" scrollHeight="200px" style="width: 600px">
<Column field="vin" header="Vin" headerStyle="width: 250px" columnKey="vin_1"></Column> <Column field="vin" header="Vin" headerStyle="width: 250px" columnKey="vin_1"></Column>
@ -82,12 +91,58 @@ export default {
data() { data() {
return { return {
cars: null, cars: null,
frozenCars: null frozenCars: null,
lazyCars: null,
lazyTotalRecords: 0
} }
}, },
carService: null, carService: null,
inmemoryData: null,
created() { created() {
this.carService = new CarService(); this.carService = new CarService();
this.inmemoryData = [
{"brand": "VW", "year": 2012, "color": "Orange"},
{"brand": "Audi", "year": 2011, "color": "Black"},
{"brand": "Renault", "year": 2005, "color": "Gray"},
{"brand": "BMW", "year": 2003, "color": "Blue"},
{"brand": "Mercedes", "year": 1995, "color": "Orange"},
{"brand": "Volvo", "year": 2005, "color": "Black"},
{"brand": "Honda", "year": 2012, "color": "Yellow"},
{"brand": "Jaguar", "year": 2013, "color": "Orange"},
{"brand": "Ford", "year": 2000, "color": "Black"},
{"brand": "Fiat", "year": 2013, "color": "Red"},
{"brand": "VW", "year": 2012, "color": "Orange"},
{"brand": "Audi", "year": 2011, "color": "Black"},
{"brand": "Renault", "year": 2005, "color": "Gray"},
{"brand": "BMW", "year": 2003, "color": "Blue"},
{"brand": "Mercedes", "year": 1995, "color": "Orange"},
{"brand": "Volvo", "year": 2005, "color": "Black"},
{"brand": "Honda", "year": 2012, "color": "Yellow"},
{"brand": "Jaguar", "year": 2013, "color": "Orange"},
{"brand": "Ford", "year": 2000, "color": "Black"},
{"brand": "Fiat", "year": 2013, "color": "Red"},
{"brand": "VW", "year": 2012, "color": "Orange"},
{"brand": "Audi", "year": 2011, "color": "Black"},
{"brand": "Renault", "year": 2005, "color": "Gray"},
{"brand": "BMW", "year": 2003, "color": "Blue"},
{"brand": "Mercedes", "year": 1995, "color": "Orange"},
{"brand": "Volvo", "year": 2005, "color": "Black"},
{"brand": "Honda", "year": 2012, "color": "Yellow"},
{"brand": "Jaguar", "year": 2013, "color": "Orange"},
{"brand": "Ford", "year": 2000, "color": "Black"},
{"brand": "Fiat", "year": 2013, "color": "Red"},
{"brand": "VW", "year": 2012, "color": "Orange"},
{"brand": "Audi", "year": 2011, "color": "Black"},
{"brand": "Renault", "year": 2005, "color": "Gray"},
{"brand": "BMW", "year": 2003, "color": "Blue"},
{"brand": "Mercedes", "year": 1995, "color": "Orange"},
{"brand": "Volvo", "year": 2005, "color": "Black"},
{"brand": "Honda", "year": 2012, "color": "Yellow"},
{"brand": "Jaguar", "year": 2013, "color": "Orange"},
{"brand": "Ford", "year": 2000, "color": "Black"},
{"brand": "Fiat", "year": 2013, "color": "Red"}
];
}, },
mounted() { mounted() {
this.carService.getCarsLarge().then(data => this.cars = data); this.carService.getCarsLarge().then(data => this.cars = data);
@ -96,6 +151,34 @@ export default {
{brand: "BMW", year: 2013, color: "Grey", vin: "fh2uf23"}, {brand: "BMW", year: 2013, color: "Grey", vin: "fh2uf23"},
{brand: "Chevrolet", year: 2011, color: "Black", vin: "4525g23"} {brand: "Chevrolet", year: 2011, color: "Black", vin: "4525g23"}
]; ];
setTimeout(() => {
this.lazyCars = this.loadChunk(0, 40);
this.lazyTotalRecords = 250000;
}, 250);
},
methods: {
loadChunk(index, length) {
let chunk = [];
for (let i = 0; i < length; i++) {
chunk[i] = {...this.inmemoryData[i], ...{vin: (index + i)}};
}
return chunk;
},
onVirtualScroll(event) {
/*
For demo purposes keep loading the same dataset,
in a real production application, this data should come from server by building the query with LazyLoadEvent options
*/
setTimeout(() => {
//last chunk
if (event.first === 249980)
this.lazyCars = this.loadChunk(event.first, 20)
else
this.lazyCars = this.loadChunk(event.first, event.rows)
}, 250);
}
}, },
components: { components: {
'DataTableSubMenu': DataTableSubMenu 'DataTableSubMenu': DataTableSubMenu