Add DataManager

- refactor datatable to use datamanager
This commit is contained in:
Faris Ansari 2017-10-29 13:15:52 +05:30
parent b409b849d4
commit 11407c2efd
6 changed files with 473 additions and 178 deletions

View File

@ -27,7 +27,7 @@
"parser": "babel-eslint",
"plugins": [
],
"rules": {
@ -150,7 +150,7 @@
"no-unreachable": 2,
"no-unused-expressions": 0,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-use-before-define": 2,
"no-use-before-define": 0,
"no-var": 0,
"no-void": 0,
"no-warning-comments": 0,

View File

@ -115,8 +115,14 @@ var _createClass = function () { function defineProperties(target, props) { for
var _utils = __webpack_require__(2);
var _datamanager = __webpack_require__(9);
var _datamanager2 = _interopRequireDefault(_datamanager);
__webpack_require__(3);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var DEFAULT_OPTIONS = {
@ -127,9 +133,9 @@ var DEFAULT_OPTIONS = {
},
editing: null,
addSerialNoColumn: true,
addCheckboxColumn: true,
enableClusterize: true,
enableLogs: false,
addCheckbox: true
enableLogs: false
};
var DataTable = function () {
@ -144,6 +150,8 @@ var DataTable = function () {
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
this.events = this.options.events;
this.datamanager = new _datamanager2.default(this.options);
if (this.options.data) {
this.refresh(this.options.data);
}
@ -162,7 +170,13 @@ var DataTable = function () {
}, {
key: 'refresh',
value: function refresh(data) {
this.data = this.prepareData(data);
this.datamanager.init(data);
this.render();
}
}, {
key: 'appendRows',
value: function appendRows(rows) {
this.datamanager.appendRows(rows);
this.render();
}
}, {
@ -181,8 +195,9 @@ var DataTable = function () {
}, {
key: 'renderHeader',
value: function renderHeader() {
// fixed header
this.header.html((0, _utils.getHeaderHTML)(this.data.columns));
var columns = this.datamanager.getColumns();
this.header.html((0, _utils.getHeaderHTML)(columns));
}
}, {
key: 'renderBody',
@ -196,12 +211,15 @@ var DataTable = function () {
}, {
key: 'renderBodyHTML',
value: function renderBodyHTML() {
// scrollable body
this.bodyScrollable.html('\n <table class="data-table-body table table-bordered">\n ' + (0, _utils.getBodyHTML)(this.data.rows) + '\n </table>\n ');
var rows = this.datamanager.getRows();
this.bodyScrollable.html('\n <table class="data-table-body table table-bordered">\n ' + (0, _utils.getBodyHTML)(rows) + '\n </table>\n ');
}
}, {
key: 'renderBodyWithClusterize',
value: function renderBodyWithClusterize() {
var self = this;
// empty body
this.bodyScrollable.html('\n <table class="data-table-body table table-bordered">\n ' + (0, _utils.getBodyHTML)([]) + '\n </table>\n ');
@ -209,12 +227,10 @@ var DataTable = function () {
this.pageLength = 1000;
this.end = this.start + this.pageLength;
var initialData = this.getDataForClusterize(
// only append ${this.pageLength} rows in the beginning
// defer remaining rows
this.data.rows.slice(this.start, this.end));
var self = this;
// only append ${this.pageLength} rows in the beginning,
// defer remaining
var rows = this.datamanager.getRows(this.start, this.end);
var initialData = this.getDataForClusterize(rows);
this.clusterize = new Clusterize({
rows: initialData,
@ -234,15 +250,16 @@ var DataTable = function () {
value: function appendRemainingData() {
var dataAppended = this.pageLength;
var promises = [];
var rowCount = this.datamanager.getRowCount();
while (dataAppended + this.pageLength < this.data.rows.length) {
while (dataAppended + this.pageLength < rowCount) {
this.start = this.end;
this.end = this.start + this.pageLength;
promises.push(this.appendNextPagePromise(this.start, this.end));
dataAppended += this.pageLength;
}
if (this.data.rows.length % this.pageLength > 0) {
if (rowCount % this.pageLength > 0) {
// last page
this.start = this.end;
this.end = this.start + this.pageLength;
@ -260,7 +277,7 @@ var DataTable = function () {
return new Promise(function (resolve) {
setTimeout(function () {
var rows = _this.data.rows.slice(start, end);
var rows = _this.datamanager.getRows(start, end);
var data = _this.getDataForClusterize(rows);
_this.clusterize.append(data);
@ -299,56 +316,6 @@ var DataTable = function () {
$cell.replaceWith($newCell);
}
}, {
key: 'prepareData',
value: function prepareData(data) {
// cache original data passed
this._data = data;
var columns = data.columns,
rows = data.rows;
if (this.options.addSerialNoColumn) {
var serialNoColumn = {
content: 'Sr. No',
resizable: false
};
columns.unshift(serialNoColumn);
rows = rows.map(function (row, i) {
var val = i + 1 + '';
return [val].concat(row);
});
}
if (this.options.addCheckbox) {
var addCheckboxColumn = {
content: '<input type="checkbox" />',
resizable: false,
sortable: false,
editable: false
};
columns.unshift(addCheckboxColumn);
rows = rows.map(function (row, i) {
// make copy of object, else it will be mutated
var val = Object.assign({}, addCheckboxColumn);
return [val].concat(row);
});
}
var _columns = (0, _utils.prepareRowHeader)(columns);
var _rows = (0, _utils.prepareRows)(rows);
return {
columns: _columns,
rows: _rows
};
}
}, {
key: 'bindEvents',
value: function bindEvents() {
@ -657,26 +624,7 @@ var DataTable = function () {
var sortAction = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'none';
colIndex = +colIndex;
this.data.rows.sort(function (a, b) {
var _aIndex = a[0].rowIndex;
var _bIndex = b[0].rowIndex;
var _a = a[colIndex].content;
var _b = b[colIndex].content;
if (sortAction === 'none') {
return _aIndex - _bIndex;
} else if (sortAction === 'asc') {
if (_a < _b) return -1;
if (_a > _b) return 1;
if (_a === _b) return 0;
} else if (sortAction === 'desc') {
if (_a < _b) return 1;
if (_a > _b) return -1;
if (_a === _b) return 0;
}
return 0;
});
this.datamanager.sortRows(colIndex, sortAction);
}
}, {
key: 'bindCheckbox',
@ -707,6 +655,8 @@ var DataTable = function () {
}, {
key: 'getCheckedRows',
value: function getCheckedRows() {
this.checkMap = this.checkMap || [];
return this.checkMap.map(function (c, rowIndex) {
if (c) {
return rowIndex;
@ -1009,7 +959,7 @@ function prepareColumns(columns) {
var _columns = columns.map(prepareColumn);
return _columns.map(function (col) {
return Object.assign(col, props);
return Object.assign({}, col, props);
});
}
@ -1706,6 +1656,229 @@ module.exports = function (css) {
module.exports = {"name":"frappe-datatable","version":"0.0.1","description":"A modern datatable library for the web","main":"lib/frappe-datatable.js","scripts":{"build":"webpack --env build","dev":"webpack --progress --colors --watch --env dev","test":"mocha --compilers js:babel-core/register --colors ./test/*.spec.js","test:watch":"mocha --compilers js:babel-core/register --colors -w ./test/*.spec.js"},"devDependencies":{"babel-cli":"6.24.1","babel-core":"6.24.1","babel-eslint":"7.2.3","babel-loader":"7.0.0","babel-plugin-add-module-exports":"0.2.1","babel-preset-es2015":"6.24.1","chai":"3.5.0","css-loader":"^0.28.7","eslint":"3.19.0","eslint-loader":"1.7.1","mocha":"3.3.0","node-sass":"^4.5.3","sass-loader":"^6.0.6","style-loader":"^0.18.2","webpack":"^3.1.0","yargs":"7.1.0"},"repository":{"type":"git","url":"https://github.com/frappe/datatable.git"},"keywords":["webpack","es6","starter","library","universal","umd","commonjs"],"author":"Faris Ansari","license":"MIT","bugs":{"url":"https://github.com/frappe/datatable/issues"},"homepage":"https://frappe.github.io/datatable","dependencies":{"bootstrap":"^4.0.0-beta","popper.js":"^1.12.5"}}
/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var DataManager = function () {
function DataManager(options) {
_classCallCheck(this, DataManager);
this.options = options;
this._data = {};
this.rowCount = 0;
}
_createClass(DataManager, [{
key: 'init',
value: function init(data) {
var columns = data.columns,
rows = data.rows;
this.columns = this.prepareColumns(columns);
this.rows = this.prepareRows(rows);
}
}, {
key: 'prepareColumns',
value: function prepareColumns(columns) {
if (!Array.isArray(columns)) {
throw new TypeError('`columns` must be an array');
}
if (this.options.addSerialNoColumn && !this._serialNoColumnAdded) {
var val = {
content: 'Sr. No',
resizable: false
};
columns = [val].concat(columns);
this._serialNoColumnAdded = true;
}
if (this.options.addCheckboxColumn && !this._checkboxColumnAdded) {
var _val = {
content: '<input type="checkbox" />',
resizable: false
};
columns = [_val].concat(columns);
this._checkboxColumnAdded = true;
}
// wrap the title in span
columns = columns.map(function (column) {
if (typeof column === 'string') {
column = '<span>' + column + '</span>';
} else if ((typeof column === 'undefined' ? 'undefined' : _typeof(column)) === 'object') {
column.content = '<span>' + column.content + '</span>';
}
return column;
});
return _prepareColumns(columns, {
isHeader: 1
});
}
}, {
key: 'prepareRows',
value: function prepareRows(rows) {
var _this = this;
if (!Array.isArray(rows) || !Array.isArray(rows[0])) {
throw new TypeError('`rows` must be an array of arrays');
}
rows = rows.map(function (row, i) {
var index = _this._getNextRowCount();
if (row.length < _this.columns.length) {
if (_this._serialNoColumnAdded) {
var val = index + 1 + '';
row = [val].concat(row);
}
if (_this._checkboxColumnAdded) {
var _val2 = '<input type="checkbox" />';
row = [_val2].concat(row);
}
}
return prepareRow(row, index);
});
return rows;
}
}, {
key: 'appendRows',
value: function appendRows(rows) {
if (Array.isArray(rows) && !Array.isArray(rows[0])) {
rows = [rows];
}
var _rows = this.prepareRows(rows);
this.rows = this.rows.concat(_rows);
}
}, {
key: 'sortRows',
value: function sortRows(colIndex, sortAction) {
this.rows.sort(function (a, b) {
var _aIndex = a[0].rowIndex;
var _bIndex = b[0].rowIndex;
var _a = a[colIndex].content;
var _b = b[colIndex].content;
if (sortAction === 'none') {
return _aIndex - _bIndex;
} else if (sortAction === 'asc') {
if (_a < _b) return -1;
if (_a > _b) return 1;
if (_a === _b) return 0;
} else if (sortAction === 'desc') {
if (_a < _b) return 1;
if (_a > _b) return -1;
if (_a === _b) return 0;
}
return 0;
});
}
}, {
key: 'reverseArray',
value: function reverseArray(array) {
var left = null;
var right = null;
for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
var temporary = array[left];
array[left] = array[right];
array[right] = temporary;
}
}
}, {
key: 'getRowCount',
value: function getRowCount() {
return this.rowCount;
}
}, {
key: '_getNextRowCount',
value: function _getNextRowCount() {
var val = this.rowCount;
this.rowCount++;
return val;
}
}, {
key: 'getRows',
value: function getRows(start, end) {
return this.rows.slice(start, end);
}
}, {
key: 'getColumns',
value: function getColumns() {
return this.columns;
}
}, {
key: 'get',
value: function get() {
return {
columns: this.columns,
rows: this.rows
};
}
}]);
return DataManager;
}();
exports.default = DataManager;
function _prepareColumns(columns) {
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _columns = columns.map(prepareCell);
return _columns.map(function (col) {
return Object.assign({}, col, props);
});
}
function prepareRow(row, i) {
return _prepareColumns(row, {
rowIndex: i
});
}
function prepareCell(col, i) {
if (typeof col === 'string') {
col = {
content: col
};
}
return Object.assign(col, {
colIndex: i
});
}
module.exports = exports['default'];
/***/ })
/******/ ]);
});

File diff suppressed because one or more lines are too long

177
src/datamanager.js Normal file
View File

@ -0,0 +1,177 @@
export default class DataManager {
constructor(options) {
this.options = options;
this._data = {};
this.rowCount = 0;
}
init(data) {
let { columns, rows } = data;
this.columns = this.prepareColumns(columns);
this.rows = this.prepareRows(rows);
}
prepareColumns(columns) {
if (!Array.isArray(columns)) {
throw new TypeError('`columns` must be an array');
}
if (this.options.addSerialNoColumn && !this._serialNoColumnAdded) {
const val = {
content: 'Sr. No',
resizable: false
};
columns = [val].concat(columns);
this._serialNoColumnAdded = true;
}
if (this.options.addCheckboxColumn && !this._checkboxColumnAdded) {
const val = {
content: '<input type="checkbox" />',
resizable: false
};
columns = [val].concat(columns);
this._checkboxColumnAdded = true;
}
// wrap the title in span
columns = columns.map(column => {
if (typeof column === 'string') {
column = `<span>${column}</span>`;
} else if (typeof column === 'object') {
column.content = `<span>${column.content}</span>`;
}
return column;
});
return prepareColumns(columns, {
isHeader: 1
});
}
prepareRows(rows) {
if (!Array.isArray(rows) || !Array.isArray(rows[0])) {
throw new TypeError('`rows` must be an array of arrays');
}
rows = rows.map((row, i) => {
const index = this._getNextRowCount();
if (row.length < this.columns.length) {
if (this._serialNoColumnAdded) {
const val = (index + 1) + '';
row = [val].concat(row);
}
if (this._checkboxColumnAdded) {
const val = '<input type="checkbox" />';
row = [val].concat(row);
}
}
return prepareRow(row, index);
});
return rows;
}
appendRows(rows) {
if (Array.isArray(rows) && !Array.isArray(rows[0])) {
rows = [rows];
}
const _rows = this.prepareRows(rows);
this.rows = this.rows.concat(_rows);
}
sortRows(colIndex, sortAction) {
this.rows.sort((a, b) => {
const _aIndex = a[0].rowIndex;
const _bIndex = b[0].rowIndex;
const _a = a[colIndex].content;
const _b = b[colIndex].content;
if (sortAction === 'none') {
return _aIndex - _bIndex;
} else if (sortAction === 'asc') {
if (_a < _b) return -1;
if (_a > _b) return 1;
if (_a === _b) return 0;
} else if (sortAction === 'desc') {
if (_a < _b) return 1;
if (_a > _b) return -1;
if (_a === _b) return 0;
}
return 0;
});
}
reverseArray(array) {
let left = null;
let right = null;
for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
const temporary = array[left];
array[left] = array[right];
array[right] = temporary;
}
}
getRowCount() {
return this.rowCount;
}
_getNextRowCount() {
const val = this.rowCount;
this.rowCount++;
return val;
}
getRows(start, end) {
return this.rows.slice(start, end);
}
getColumns() {
return this.columns;
}
get() {
return {
columns: this.columns,
rows: this.rows
};
}
}
function prepareColumns(columns, props = {}) {
const _columns = columns.map(prepareCell);
return _columns.map(col => Object.assign({}, col, props));
}
function prepareRow(row, i) {
return prepareColumns(row, {
rowIndex: i
});
}
function prepareCell(col, i) {
if (typeof col === 'string') {
col = {
content: col
};
}
return Object.assign(col, {
colIndex: i
});
}

View File

@ -4,11 +4,11 @@ import {
getBodyHTML,
getRowHTML,
getColumnHTML,
prepareRowHeader,
buildCSSRule,
prepareRows,
getDefault
} from './utils.js';
} from './utils';
import DataManager from './datamanager';
import './style.scss';
@ -20,9 +20,9 @@ const DEFAULT_OPTIONS = {
},
editing: null,
addSerialNoColumn: true,
addCheckboxColumn: true,
enableClusterize: true,
enableLogs: false,
addCheckbox: true
enableLogs: false
};
export default class DataTable {
@ -36,6 +36,8 @@ export default class DataTable {
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
this.events = this.options.events;
this.datamanager = new DataManager(this.options);
if (this.options.data) {
this.refresh(this.options.data);
}
@ -63,7 +65,12 @@ export default class DataTable {
}
refresh(data) {
this.data = this.prepareData(data);
this.datamanager.init(data);
this.render();
}
appendRows(rows) {
this.datamanager.appendRows(rows);
this.render();
}
@ -80,8 +87,9 @@ export default class DataTable {
}
renderHeader() {
// fixed header
this.header.html(getHeaderHTML(this.data.columns));
const columns = this.datamanager.getColumns();
this.header.html(getHeaderHTML(columns));
}
renderBody() {
@ -93,15 +101,18 @@ export default class DataTable {
}
renderBodyHTML() {
// scrollable body
const rows = this.datamanager.getRows();
this.bodyScrollable.html(`
<table class="data-table-body table table-bordered">
${getBodyHTML(this.data.rows)}
${getBodyHTML(rows)}
</table>
`);
}
renderBodyWithClusterize() {
const self = this;
// empty body
this.bodyScrollable.html(`
<table class="data-table-body table table-bordered">
@ -113,13 +124,10 @@ export default class DataTable {
this.pageLength = 1000;
this.end = this.start + this.pageLength;
const initialData = this.getDataForClusterize(
// only append ${this.pageLength} rows in the beginning
// defer remaining rows
this.data.rows.slice(this.start, this.end)
);
const self = this;
// only append ${this.pageLength} rows in the beginning,
// defer remaining
const rows = this.datamanager.getRows(this.start, this.end);
const initialData = this.getDataForClusterize(rows);
this.clusterize = new Clusterize({
rows: initialData,
@ -138,15 +146,16 @@ export default class DataTable {
appendRemainingData() {
let dataAppended = this.pageLength;
const promises = [];
const rowCount = this.datamanager.getRowCount();
while (dataAppended + this.pageLength < this.data.rows.length) {
while (dataAppended + this.pageLength < rowCount) {
this.start = this.end;
this.end = this.start + this.pageLength;
promises.push(this.appendNextPagePromise(this.start, this.end));
dataAppended += this.pageLength;
}
if (this.data.rows.length % this.pageLength > 0) {
if (rowCount % this.pageLength > 0) {
// last page
this.start = this.end;
this.end = this.start + this.pageLength;
@ -161,7 +170,7 @@ export default class DataTable {
appendNextPagePromise(start, end) {
return new Promise(resolve => {
setTimeout(() => {
const rows = this.data.rows.slice(start, end);
const rows = this.datamanager.getRows(start, end);
const data = this.getDataForClusterize(rows);
this.clusterize.append(data);
@ -195,53 +204,6 @@ export default class DataTable {
$cell.replaceWith($newCell);
}
prepareData(data) {
// cache original data passed
this._data = data;
let { columns, rows } = data;
if (this.options.addSerialNoColumn) {
const serialNoColumn = {
content: 'Sr. No',
resizable: false
};
columns.unshift(serialNoColumn);
rows = rows.map((row, i) => {
const val = (i + 1) + '';
return [val].concat(row);
});
}
if (this.options.addCheckbox) {
const addCheckboxColumn = {
content: '<input type="checkbox" />',
resizable: false,
sortable: false,
editable: false
};
columns.unshift(addCheckboxColumn);
rows = rows.map((row, i) => {
// make copy of object, else it will be mutated
const val = Object.assign({}, addCheckboxColumn);
return [val].concat(row);
});
}
const _columns = prepareRowHeader(columns);
const _rows = prepareRows(rows);
return {
columns: _columns,
rows: _rows
};
}
bindEvents() {
this.bindFocusCell();
this.bindEditCell();
@ -519,26 +481,7 @@ export default class DataTable {
sortRows(colIndex, sortAction = 'none') {
colIndex = +colIndex;
this.data.rows.sort((a, b) => {
const _aIndex = a[0].rowIndex;
const _bIndex = b[0].rowIndex;
const _a = a[colIndex].content;
const _b = b[colIndex].content;
if (sortAction === 'none') {
return _aIndex - _bIndex;
} else if (sortAction === 'asc') {
if (_a < _b) return -1;
if (_a > _b) return 1;
if (_a === _b) return 0;
} else if (sortAction === 'desc') {
if (_a < _b) return 1;
if (_a > _b) return -1;
if (_a === _b) return 0;
}
return 0;
});
this.datamanager.sortRows(colIndex, sortAction);
}
bindCheckbox() {
@ -563,6 +506,8 @@ export default class DataTable {
}
getCheckedRows() {
this.checkMap = this.checkMap || [];
return this.checkMap
.map((c, rowIndex) => {
if (c) {

View File

@ -95,7 +95,7 @@ function prepareColumn(col, i) {
function prepareColumns(columns, props = {}) {
const _columns = columns.map(prepareColumn);
return _columns.map(col => Object.assign(col, props));
return _columns.map(col => Object.assign({}, col, props));
}
function prepareRowHeader(columns) {