feat: 🎸 Use HyperList instead of Clusterize to render rows

Clusterize failed to load rows if they were above 1000, this may be a
problem of datatable itself, since it has a complex DOM structure, but
hyperlist works better in it's case.
This commit is contained in:
Faris Ansari 2018-10-10 14:31:53 +05:30
parent f790a6729a
commit 51b112687e
9 changed files with 64 additions and 110 deletions

View File

@ -13,6 +13,7 @@
"cy:open": "cypress open",
"cy:run": "cypress run",
"test": "start-server-and-test cy:server http://localhost:8989 cy:run",
"test-local": "start-server-and-test cy:server http://localhost:8989 cy:open",
"travis-deploy-once": "travis-deploy-once",
"semantic-release": "semantic-release",
"lint": "eslint src",
@ -64,7 +65,7 @@
},
"homepage": "https://frappe.github.io/datatable",
"dependencies": {
"clusterize.js": "^0.18.0",
"hyperlist": "^1.0.0-beta",
"lodash": "^4.17.5",
"sortablejs": "^1.7.0"
},

View File

@ -1,6 +1,5 @@
import Clusterize from 'clusterize.js';
import HyperList from 'hyperlist';
import $ from './dom';
import { nextTick } from './utils';
export default class BodyRenderer {
constructor(instance) {
@ -11,59 +10,45 @@ export default class BodyRenderer {
this.cellmanager = instance.cellmanager;
this.bodyScrollable = instance.bodyScrollable;
this.log = instance.log;
this.appendRemainingData = nextTick(this.appendRemainingData, this);
}
renderRows(rows) {
let config = {
itemHeight: 40,
total: rows.length,
generate: (index) => {
const el = document.createElement('div');
const rowHTML = this.rowmanager.getRowHTML(rows[index], rows[index].meta);
el.innerHTML = rowHTML;
return el.children[0];
}
};
this.hyperlist.refresh($('.dt-body', this.bodyScrollable), config);
}
render() {
if (this.options.clusterize) {
this.renderBodyWithClusterize();
} else {
this.renderBodyHTML();
}
}
renderBodyHTML() {
const rows = this.datamanager.getRowsForView();
this.bodyScrollable.innerHTML = this.getBodyHTML(rows);
this.instance.setDimensions();
this.restoreState();
}
let config = {
itemHeight: 40,
total: rows.length,
generate: (index) => {
const el = document.createElement('div');
const rowHTML = this.rowmanager.getRowHTML(rows[index], rows[index].meta);
el.innerHTML = rowHTML;
return el.children[0];
}
};
renderBodyWithClusterize() {
// first page
const rows = this.datamanager.getRowsForView(0, 20);
let initialData = this.getDataForClusterize(rows);
if (initialData.length === 0) {
initialData = [this.getNoDataHTML()];
}
if (!this.clusterize) {
// empty body
this.bodyScrollable.innerHTML = this.getBodyHTML([]);
// first 20 rows will appended
// rest of them in nextTick
this.clusterize = new Clusterize({
rows: initialData,
scrollElem: this.bodyScrollable,
contentElem: $('tbody', this.bodyScrollable),
callbacks: {
clusterChanged: () => this.restoreState()
},
/* eslint-disable */
show_no_data_row: false,
/* eslint-enable */
});
// setDimensions requires atleast 1 row to exist in dom
this.instance.setDimensions();
if (!this.hyperlist) {
this.bodyScrollable.innerHTML = '<div class="dt-body"></div>';
this.hyperlist = new HyperList($('.dt-body', this.bodyScrollable), config);
} else {
this.clusterize.update(initialData);
this.renderRows(rows);
}
this.appendRemainingData();
// setDimensions requires atleast 1 row to exist in dom
this.instance.setDimensions();
}
restoreState() {
@ -73,12 +58,6 @@ export default class BodyRenderer {
this.cellmanager.focusCellOnClusterChanged();
}
appendRemainingData() {
const rows = this.datamanager.getRowsForView(20);
const data = this.getDataForClusterize(rows);
this.clusterize.append(data);
}
showToastMessage(message, hideAfter) {
this.instance.toastMessage.innerHTML = this.getToastMessageHTML(message);
@ -99,11 +78,11 @@ export default class BodyRenderer {
getBodyHTML(rows) {
return `
<table class="dt-body">
<tbody>
<div class="dt-body">
<div>
${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
</tbody>
</table>
</div>
</div>
`;
}

View File

@ -749,9 +749,9 @@ export default class CellManager {
].join(' ');
return `
<td class="${className}" ${dataAttr} tabindex="0">
<div class="${className}" ${dataAttr} tabindex="0">
${this.getCellContent(cell)}
</td>
</div>
`;
}

View File

@ -17,14 +17,15 @@ export default class ColumnManager {
'style',
'wrapper',
'rowmanager',
'bodyScrollable'
'bodyScrollable',
'bodyRenderer'
]);
this.bindEvents();
}
renderHeader() {
this.header.innerHTML = '<thead></thead>';
this.header.innerHTML = '<div></div>';
this.refreshHeader();
}
@ -32,7 +33,7 @@ export default class ColumnManager {
const columns = this.datamanager.getColumns();
// refresh html
$('thead', this.header).innerHTML = this.getHeaderHTML(columns);
$('div', this.header).innerHTML = this.getHeaderHTML(columns);
this.$filterRow = $('.dt-row-filter', this.header);
if (this.$filterRow) {
@ -282,11 +283,10 @@ export default class ColumnManager {
applyFilter(keyword, colIndex) {
this.datamanager.filterRows(keyword, colIndex)
.then(({
rowsToHide,
rowsToShow
}) => {
this.rowmanager.hideRows(rowsToHide);
this.rowmanager.showRows(rowsToShow);
const rows = rowsToShow.map(rowIndex => this.datamanager.getRow(rowIndex));
this.bodyRenderer.renderRows(rows);
});
}

View File

@ -69,8 +69,8 @@ class DataTable {
prepareDom() {
this.wrapper.innerHTML = `
<div class="datatable">
<table class="dt-header">
</table>
<div class="dt-header">
</div>
<div class="dt-scrollable">
</div>
<div class="dt-freeze">

View File

@ -279,9 +279,9 @@ export default class RowManager {
}
return `
<tr class="dt-row dt-row-${rowIdentifier}" ${dataAttr}>
<div class="dt-row dt-row-${rowIdentifier}" ${dataAttr}>
${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
</tr>
</div>
`;
}

View File

@ -43,7 +43,7 @@
}
.dt-scrollable {
max-height: 40vw;
height: 40vw;
overflow: auto;
border-bottom: 1px solid var(--dt-border-color);
@ -60,6 +60,8 @@
}
.dt-row {
display: flex;
&--highlight .dt-cell {
background-color: var(--dt-selection-highlight-color);
}
@ -75,6 +77,8 @@
.dt-cell {
border: 1px solid var(--dt-border-color);
border-bottom: none;
border-right: none;
position: relative;
outline: none;
padding: 0;
@ -141,6 +145,10 @@
background-color: var(--dt-header-cell-bg);
}
&--header:last-child {
border-right: 1px solid var(--dt-border-color);
}
&--header &__content {
padding-right: var(--dt-spacer-3);
font-weight: bold;

View File

@ -119,33 +119,16 @@ export default class Style {
}
setDimensions() {
this.setHeaderStyle();
this.setupMinWidth();
this.setupNaturalColumnWidth();
this.setupColumnWidth();
this.distributeRemainingWidth();
this.setColumnStyle();
this.compensateScrollbarWidth();
this.setDefaultCellHeight();
this.setBodyStyle();
}
setHeaderStyle() {
if (this.options.layout === 'fluid') {
// setting width as 0 will ensure that the
// header doesn't take the available space
$.style(this.header, {
width: 0
});
}
$.style(this.header, {
margin: 0
});
}
setupMinWidth() {
$.each('.dt-cell--header', this.header).map(col => {
const { colIndex } = $.data(col);
@ -228,7 +211,7 @@ export default class Style {
}
compensateScrollbarWidth() {
if (!$.hasVerticalOverflow(this.bodyScrollable)) return;
if (!$.hasVerticalOverflow($('.dt-body', this.bodyScrollable))) return;
requestAnimationFrame(() => {
const scrollbarWidth = $.scrollbarWidth();
@ -313,26 +296,9 @@ export default class Style {
width: width + 'px'
});
const $body = $('.dt-body', this.bodyScrollable);
if ($body) {
$.style($body, {
height: '0px'
});
$.style($('tbody', $body), {
height: '100%'
});
}
$.style(this.bodyScrollable, {
marginTop: $.style(this.header, 'height') + 'px'
});
$.style($('table', this.bodyScrollable), {
margin: 0,
width: '100%'
});
});
}

View File

@ -1493,11 +1493,6 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f"
integrity sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=
clusterize.js@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/clusterize.js/-/clusterize.js-0.18.0.tgz#32dce7267c5e934bfb205ba65a98760005041331"
integrity sha512-13AqtsPStx5OawyggucdPawJaJmPl56HbG32Urya79VE39T7ZlpgVUGIOPwr9TIwf/duZAXjhXDQxC2eiMe6AA==
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@ -3331,6 +3326,11 @@ https-proxy-agent@^2.2.0, https-proxy-agent@^2.2.1:
agent-base "^4.1.0"
debug "^3.1.0"
hyperlist@^1.0.0-beta:
version "1.0.0-beta"
resolved "https://registry.yarnpkg.com/hyperlist/-/hyperlist-1.0.0-beta.tgz#2cbbd77f4498c2ecc290b7f3c6745b3f0288247e"
integrity sha1-LLvXf0SYwuzCkLfzxnRbPwKIJH4=
iconv-lite@^0.4.17:
version "0.4.21"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798"