Merge pull request #49 from frappe/hyperlist
feat: 🎸 Use HyperList instead of Clusterize to render rows
This commit is contained in:
commit
cbe9f4858b
@ -13,6 +13,7 @@
|
|||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"cy:run": "cypress run",
|
"cy:run": "cypress run",
|
||||||
"test": "start-server-and-test cy:server http://localhost:8989 cy: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",
|
"travis-deploy-once": "travis-deploy-once",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
@ -64,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://frappe.github.io/datatable",
|
"homepage": "https://frappe.github.io/datatable",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clusterize.js": "^0.18.0",
|
"hyperlist": "^1.0.0-beta",
|
||||||
"lodash": "^4.17.5",
|
"lodash": "^4.17.5",
|
||||||
"sortablejs": "^1.7.0"
|
"sortablejs": "^1.7.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import Clusterize from 'clusterize.js';
|
import HyperList from 'hyperlist';
|
||||||
import $ from './dom';
|
import $ from './dom';
|
||||||
import { nextTick } from './utils';
|
|
||||||
|
|
||||||
export default class BodyRenderer {
|
export default class BodyRenderer {
|
||||||
constructor(instance) {
|
constructor(instance) {
|
||||||
@ -11,59 +10,45 @@ export default class BodyRenderer {
|
|||||||
this.cellmanager = instance.cellmanager;
|
this.cellmanager = instance.cellmanager;
|
||||||
this.bodyScrollable = instance.bodyScrollable;
|
this.bodyScrollable = instance.bodyScrollable;
|
||||||
this.log = instance.log;
|
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() {
|
render() {
|
||||||
if (this.options.clusterize) {
|
|
||||||
this.renderBodyWithClusterize();
|
|
||||||
} else {
|
|
||||||
this.renderBodyHTML();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderBodyHTML() {
|
|
||||||
const rows = this.datamanager.getRowsForView();
|
const rows = this.datamanager.getRowsForView();
|
||||||
|
|
||||||
this.bodyScrollable.innerHTML = this.getBodyHTML(rows);
|
let config = {
|
||||||
this.instance.setDimensions();
|
itemHeight: 40,
|
||||||
this.restoreState();
|
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() {
|
if (!this.hyperlist) {
|
||||||
// first page
|
this.bodyScrollable.innerHTML = '<div class="dt-body"></div>';
|
||||||
const rows = this.datamanager.getRowsForView(0, 20);
|
this.hyperlist = new HyperList($('.dt-body', this.bodyScrollable), config);
|
||||||
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();
|
|
||||||
} else {
|
} else {
|
||||||
this.clusterize.update(initialData);
|
this.renderRows(rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appendRemainingData();
|
// setDimensions requires atleast 1 row to exist in dom
|
||||||
|
this.instance.setDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreState() {
|
restoreState() {
|
||||||
@ -73,12 +58,6 @@ export default class BodyRenderer {
|
|||||||
this.cellmanager.focusCellOnClusterChanged();
|
this.cellmanager.focusCellOnClusterChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
appendRemainingData() {
|
|
||||||
const rows = this.datamanager.getRowsForView(20);
|
|
||||||
const data = this.getDataForClusterize(rows);
|
|
||||||
this.clusterize.append(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
showToastMessage(message, hideAfter) {
|
showToastMessage(message, hideAfter) {
|
||||||
this.instance.toastMessage.innerHTML = this.getToastMessageHTML(message);
|
this.instance.toastMessage.innerHTML = this.getToastMessageHTML(message);
|
||||||
|
|
||||||
@ -99,11 +78,11 @@ export default class BodyRenderer {
|
|||||||
|
|
||||||
getBodyHTML(rows) {
|
getBodyHTML(rows) {
|
||||||
return `
|
return `
|
||||||
<table class="dt-body">
|
<div class="dt-body">
|
||||||
<tbody>
|
<div>
|
||||||
${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
|
${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -749,9 +749,9 @@ export default class CellManager {
|
|||||||
].join(' ');
|
].join(' ');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<td class="${className}" ${dataAttr} tabindex="0">
|
<div class="${className}" ${dataAttr} tabindex="0">
|
||||||
${this.getCellContent(cell)}
|
${this.getCellContent(cell)}
|
||||||
</td>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,14 +17,15 @@ export default class ColumnManager {
|
|||||||
'style',
|
'style',
|
||||||
'wrapper',
|
'wrapper',
|
||||||
'rowmanager',
|
'rowmanager',
|
||||||
'bodyScrollable'
|
'bodyScrollable',
|
||||||
|
'bodyRenderer'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader() {
|
renderHeader() {
|
||||||
this.header.innerHTML = '<thead></thead>';
|
this.header.innerHTML = '<div></div>';
|
||||||
this.refreshHeader();
|
this.refreshHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ export default class ColumnManager {
|
|||||||
const columns = this.datamanager.getColumns();
|
const columns = this.datamanager.getColumns();
|
||||||
|
|
||||||
// refresh html
|
// refresh html
|
||||||
$('thead', this.header).innerHTML = this.getHeaderHTML(columns);
|
$('div', this.header).innerHTML = this.getHeaderHTML(columns);
|
||||||
|
|
||||||
this.$filterRow = $('.dt-row-filter', this.header);
|
this.$filterRow = $('.dt-row-filter', this.header);
|
||||||
if (this.$filterRow) {
|
if (this.$filterRow) {
|
||||||
@ -282,11 +283,10 @@ export default class ColumnManager {
|
|||||||
applyFilter(keyword, colIndex) {
|
applyFilter(keyword, colIndex) {
|
||||||
this.datamanager.filterRows(keyword, colIndex)
|
this.datamanager.filterRows(keyword, colIndex)
|
||||||
.then(({
|
.then(({
|
||||||
rowsToHide,
|
|
||||||
rowsToShow
|
rowsToShow
|
||||||
}) => {
|
}) => {
|
||||||
this.rowmanager.hideRows(rowsToHide);
|
const rows = rowsToShow.map(rowIndex => this.datamanager.getRow(rowIndex));
|
||||||
this.rowmanager.showRows(rowsToShow);
|
this.bodyRenderer.renderRows(rows);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -69,8 +69,8 @@ class DataTable {
|
|||||||
prepareDom() {
|
prepareDom() {
|
||||||
this.wrapper.innerHTML = `
|
this.wrapper.innerHTML = `
|
||||||
<div class="datatable">
|
<div class="datatable">
|
||||||
<table class="dt-header">
|
<div class="dt-header">
|
||||||
</table>
|
</div>
|
||||||
<div class="dt-scrollable">
|
<div class="dt-scrollable">
|
||||||
</div>
|
</div>
|
||||||
<div class="dt-freeze">
|
<div class="dt-freeze">
|
||||||
|
|||||||
@ -279,9 +279,9 @@ export default class RowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return `
|
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('')}
|
${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
|
||||||
</tr>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dt-scrollable {
|
.dt-scrollable {
|
||||||
max-height: 40vw;
|
height: 40vw;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-bottom: 1px solid var(--dt-border-color);
|
border-bottom: 1px solid var(--dt-border-color);
|
||||||
|
|
||||||
@ -60,6 +60,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dt-row {
|
.dt-row {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
&--highlight .dt-cell {
|
&--highlight .dt-cell {
|
||||||
background-color: var(--dt-selection-highlight-color);
|
background-color: var(--dt-selection-highlight-color);
|
||||||
}
|
}
|
||||||
@ -75,6 +77,8 @@
|
|||||||
|
|
||||||
.dt-cell {
|
.dt-cell {
|
||||||
border: 1px solid var(--dt-border-color);
|
border: 1px solid var(--dt-border-color);
|
||||||
|
border-bottom: none;
|
||||||
|
border-right: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -141,6 +145,10 @@
|
|||||||
background-color: var(--dt-header-cell-bg);
|
background-color: var(--dt-header-cell-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--header:last-child {
|
||||||
|
border-right: 1px solid var(--dt-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
&--header &__content {
|
&--header &__content {
|
||||||
padding-right: var(--dt-spacer-3);
|
padding-right: var(--dt-spacer-3);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
36
src/style.js
36
src/style.js
@ -119,33 +119,16 @@ export default class Style {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setDimensions() {
|
setDimensions() {
|
||||||
this.setHeaderStyle();
|
|
||||||
|
|
||||||
this.setupMinWidth();
|
this.setupMinWidth();
|
||||||
this.setupNaturalColumnWidth();
|
this.setupNaturalColumnWidth();
|
||||||
this.setupColumnWidth();
|
this.setupColumnWidth();
|
||||||
this.distributeRemainingWidth();
|
this.distributeRemainingWidth();
|
||||||
this.setColumnStyle();
|
this.setColumnStyle();
|
||||||
this.compensateScrollbarWidth();
|
this.compensateScrollbarWidth();
|
||||||
|
|
||||||
this.setDefaultCellHeight();
|
this.setDefaultCellHeight();
|
||||||
this.setBodyStyle();
|
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() {
|
setupMinWidth() {
|
||||||
$.each('.dt-cell--header', this.header).map(col => {
|
$.each('.dt-cell--header', this.header).map(col => {
|
||||||
const { colIndex } = $.data(col);
|
const { colIndex } = $.data(col);
|
||||||
@ -228,7 +211,7 @@ export default class Style {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compensateScrollbarWidth() {
|
compensateScrollbarWidth() {
|
||||||
if (!$.hasVerticalOverflow(this.bodyScrollable)) return;
|
if (!$.hasVerticalOverflow($('.dt-body', this.bodyScrollable))) return;
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const scrollbarWidth = $.scrollbarWidth();
|
const scrollbarWidth = $.scrollbarWidth();
|
||||||
@ -313,26 +296,9 @@ export default class Style {
|
|||||||
width: width + 'px'
|
width: width + 'px'
|
||||||
});
|
});
|
||||||
|
|
||||||
const $body = $('.dt-body', this.bodyScrollable);
|
|
||||||
|
|
||||||
if ($body) {
|
|
||||||
$.style($body, {
|
|
||||||
height: '0px'
|
|
||||||
});
|
|
||||||
|
|
||||||
$.style($('tbody', $body), {
|
|
||||||
height: '100%'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$.style(this.bodyScrollable, {
|
$.style(this.bodyScrollable, {
|
||||||
marginTop: $.style(this.header, 'height') + 'px'
|
marginTop: $.style(this.header, 'height') + 'px'
|
||||||
});
|
});
|
||||||
|
|
||||||
$.style($('table', this.bodyScrollable), {
|
|
||||||
margin: 0,
|
|
||||||
width: '100%'
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@ -1493,11 +1493,6 @@ clone@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f"
|
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f"
|
||||||
integrity sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=
|
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:
|
co@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
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"
|
agent-base "^4.1.0"
|
||||||
debug "^3.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:
|
iconv-lite@^0.4.17:
|
||||||
version "0.4.21"
|
version "0.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user