diff --git a/src/body-renderer.js b/src/body-renderer.js
index 5c1cb5e..17e8435 100644
--- a/src/body-renderer.js
+++ b/src/body-renderer.js
@@ -1,99 +1,101 @@
import $ from './dom';
import Clusterize from 'clusterize.js';
-import { promisify } from './utils';
+import {
+ promisify
+} from './utils';
export default class BodyRenderer {
- constructor(instance) {
- this.instance = instance;
- this.options = instance.options;
- this.datamanager = instance.datamanager;
- this.rowmanager = instance.rowmanager;
- this.cellmanager = instance.cellmanager;
- this.bodyScrollable = instance.bodyScrollable;
- this.log = instance.log;
- this.appendRemainingData = promisify(this.appendRemainingData, this);
- }
-
- render() {
- if (this.options.enableClusterize) {
- this.renderBodyWithClusterize();
- } else {
- this.renderBodyHTML();
- }
- }
-
- renderBodyHTML() {
- const rows = this.datamanager.getRows();
-
- this.bodyScrollable.innerHTML = `
-
- `;
- this.instance.setDimensions();
- this.restoreState();
- }
-
- renderBodyWithClusterize() {
- // first page
- const rows = this.datamanager.getRows(0, 20);
- const initialData = this.getDataForClusterize(rows);
-
- if (!this.clusterize) {
- // empty body
- this.bodyScrollable.innerHTML = `
-
- `;
-
- // 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 */
- no_data_text: this.options.noDataMessage,
- no_data_class: 'empty-state'
- /* eslint-enable */
- });
-
- // setDimensions requires atleast 1 row to exist in dom
- this.instance.setDimensions();
- } else {
- this.clusterize.update(initialData);
+ constructor(instance) {
+ this.instance = instance;
+ this.options = instance.options;
+ this.datamanager = instance.datamanager;
+ this.rowmanager = instance.rowmanager;
+ this.cellmanager = instance.cellmanager;
+ this.bodyScrollable = instance.bodyScrollable;
+ this.log = instance.log;
+ this.appendRemainingData = promisify(this.appendRemainingData, this);
}
- this.appendRemainingData();
- }
+ render() {
+ if (this.options.enableClusterize) {
+ this.renderBodyWithClusterize();
+ } else {
+ this.renderBodyHTML();
+ }
+ }
- restoreState() {
- this.rowmanager.highlightCheckedRows();
- this.cellmanager.selectAreaOnClusterChanged();
- this.cellmanager.focusCellOnClusterChanged();
- }
+ renderBodyHTML() {
+ const rows = this.datamanager.getRows();
- appendRemainingData() {
- const rows = this.datamanager.getRows(20);
- const data = this.getDataForClusterize(rows);
- this.clusterize.append(data);
- }
+ this.bodyScrollable.innerHTML = `
+
+ ${this.getBodyHTML(rows)}
+
+ `;
+ this.instance.setDimensions();
+ this.restoreState();
+ }
- getDataForClusterize(rows) {
- return rows.map((row) => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex }));
- }
+ renderBodyWithClusterize() {
+ // first page
+ const rows = this.datamanager.getRows(0, 20);
+ const initialData = this.getDataForClusterize(rows);
+
+ 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 */
+ no_data_text: this.options.noDataMessage,
+ no_data_class: 'empty-state'
+ /* eslint-enable */
+ });
+
+ // setDimensions requires atleast 1 row to exist in dom
+ this.instance.setDimensions();
+ } else {
+ this.clusterize.update(initialData);
+ }
+
+ this.appendRemainingData();
+ }
+
+ restoreState() {
+ this.rowmanager.highlightCheckedRows();
+ this.cellmanager.selectAreaOnClusterChanged();
+ this.cellmanager.focusCellOnClusterChanged();
+ }
+
+ appendRemainingData() {
+ const rows = this.datamanager.getRows(20);
+ const data = this.getDataForClusterize(rows);
+ this.clusterize.append(data);
+ }
+
+ getDataForClusterize(rows) {
+ return rows.map((row) => this.rowmanager.getRowHTML(row, row.meta));
+ }
+
+ getBodyHTML(rows) {
+ return `
+
+ ${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
+
+ `;
+ }
};
-
-export function getBodyHTML(rows) {
- return `
-
- ${rows.map(row => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex })).join('')}
-
- `;
-}
diff --git a/src/cellmanager.js b/src/cellmanager.js
index 12645e2..b8ea6c3 100644
--- a/src/cellmanager.js
+++ b/src/cellmanager.js
@@ -1,609 +1,657 @@
import {
- copyTextToClipboard,
- makeDataAttributeString,
- throttle
+ copyTextToClipboard,
+ makeDataAttributeString,
+ throttle
} from './utils';
import $ from './dom';
-import { getDropdownHTML } from './columnmanager';
+import {
+ getDropdownHTML
+} from './columnmanager';
export default class CellManager {
- constructor(instance) {
- this.instance = instance;
- this.wrapper = this.instance.wrapper;
- this.options = this.instance.options;
- this.style = this.instance.style;
- this.bodyScrollable = this.instance.bodyScrollable;
- this.columnmanager = this.instance.columnmanager;
- this.rowmanager = this.instance.rowmanager;
- this.datamanager = this.instance.datamanager;
- this.keyboard = this.instance.keyboard;
+ constructor(instance) {
+ this.instance = instance;
+ this.wrapper = this.instance.wrapper;
+ this.options = this.instance.options;
+ this.style = this.instance.style;
+ this.bodyScrollable = this.instance.bodyScrollable;
+ this.columnmanager = this.instance.columnmanager;
+ this.rowmanager = this.instance.rowmanager;
+ this.datamanager = this.instance.datamanager;
+ this.keyboard = this.instance.keyboard;
- this.bindEvents();
- }
+ this.bindEvents();
+ }
- bindEvents() {
- this.bindFocusCell();
- this.bindEditCell();
- this.bindKeyboardSelection();
- this.bindCopyCellContents();
- this.bindMouseEvents();
- }
+ bindEvents() {
+ this.bindFocusCell();
+ this.bindEditCell();
+ this.bindKeyboardSelection();
+ this.bindCopyCellContents();
+ this.bindMouseEvents();
+ }
- bindFocusCell() {
- this.bindKeyboardNav();
- }
+ bindFocusCell() {
+ this.bindKeyboardNav();
+ }
- bindEditCell() {
- this.$editingCell = null;
+ bindEditCell() {
+ this.$editingCell = null;
- $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
- this.activateEditing(cell);
- });
+ $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
+ this.activateEditing(cell);
+ });
+
+ this.keyboard.on('enter', (e) => {
+ if (this.$focusedCell && !this.$editingCell) {
+ // enter keypress on focused cell
+ this.activateEditing(this.$focusedCell);
+ } else if (this.$editingCell) {
+ // enter keypress on editing cell
+ this.submitEditing();
+ this.deactivateEditing();
+ }
+ });
+ }
+
+ bindKeyboardNav() {
+ const focusCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+
+ if (direction === 'left' || direction === 'shift+tab') {
+ $cell = this.getLeftCell$($cell);
+ } else if (direction === 'right' || direction === 'tab') {
+ $cell = this.getRightCell$($cell);
+ } else if (direction === 'up') {
+ $cell = this.getAboveCell$($cell);
+ } else if (direction === 'down') {
+ $cell = this.getBelowCell$($cell);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ const focusLastCell = (direction) => {
+ if (!this.$focusedCell || this.$editingCell) {
+ return false;
+ }
+
+ let $cell = this.$focusedCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+
+ if (direction === 'left') {
+ $cell = this.getLeftMostCell$(rowIndex);
+ } else if (direction === 'right') {
+ $cell = this.getRightMostCell$(rowIndex);
+ } else if (direction === 'up') {
+ $cell = this.getTopMostCell$(colIndex);
+ } else if (direction === 'down') {
+ $cell = this.getBottomMostCell$(colIndex);
+ }
+
+ this.focusCell($cell);
+ return true;
+ };
+
+ ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
+ direction => this.keyboard.on(direction, () => focusCell(direction))
+ );
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
+ );
+
+ this.keyboard.on('esc', () => {
+ this.deactivateEditing();
+ });
+
+ if (this.options.enableInlineFilters) {
+ this.keyboard.on('ctrl+f', (e) => {
+ const $cell = $.closest('.data-table-col', e.target);
+ let {
+ colIndex
+ } = $.data($cell);
+
+ this.activateFilter(colIndex);
+ return true;
+ });
+ }
+ }
+
+ bindKeyboardSelection() {
+ const getNextSelectionCursor = (direction) => {
+ let $selectionCursor = this.getSelectionCursor();
+
+ if (direction === 'left') {
+ $selectionCursor = this.getLeftCell$($selectionCursor);
+ } else if (direction === 'right') {
+ $selectionCursor = this.getRightCell$($selectionCursor);
+ } else if (direction === 'up') {
+ $selectionCursor = this.getAboveCell$($selectionCursor);
+ } else if (direction === 'down') {
+ $selectionCursor = this.getBelowCell$($selectionCursor);
+ }
+
+ return $selectionCursor;
+ };
+
+ ['left', 'right', 'up', 'down'].map(
+ direction => this.keyboard.on('shift+' + direction,
+ () => this.selectArea(getNextSelectionCursor(direction)))
+ );
+ }
+
+ bindCopyCellContents() {
+ this.keyboard.on('ctrl+c', () => {
+ this.copyCellContents(this.$focusedCell, this.$selectionCursor);
+ });
+ }
+
+ bindMouseEvents() {
+ let mouseDown = null;
+
+ $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
+ mouseDown = true;
+ this.focusCell($(e.delegatedTarget));
+ });
+
+ $.on(this.bodyScrollable, 'mouseup', () => {
+ mouseDown = false;
+ });
+
+ const selectArea = (e) => {
+ if (!mouseDown) return;
+ this.selectArea($(e.delegatedTarget));
+ };
+
+ $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle(selectArea, 50));
+ }
+
+ focusCell($cell, {
+ skipClearSelection = 0
+ } = {}) {
+ if (!$cell) return;
+
+ // don't focus if already editing cell
+ if ($cell === this.$editingCell) return;
+
+ const {
+ colIndex,
+ isHeader
+ } = $.data($cell);
+ if (isHeader) {
+ return;
+ }
+
+ const column = this.columnmanager.getColumn(colIndex);
+ if (column.focusable === false) {
+ return;
+ }
+
+ this.scrollToCell($cell);
- this.keyboard.on('enter', (e) => {
- if (this.$focusedCell && !this.$editingCell) {
- // enter keypress on focused cell
- this.activateEditing(this.$focusedCell);
- } else if (this.$editingCell) {
- // enter keypress on editing cell
- this.submitEditing();
this.deactivateEditing();
- }
- });
- }
+ if (!skipClearSelection) {
+ this.clearSelection();
+ }
- bindKeyboardNav() {
- const focusCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
+ if (this.$focusedCell) {
+ this.$focusedCell.classList.remove('selected');
+ }
- let $cell = this.$focusedCell;
+ this.$focusedCell = $cell;
+ $cell.classList.add('selected');
- if (direction === 'left' || direction === 'shift+tab') {
- $cell = this.getLeftCell$($cell);
- } else if (direction === 'right' || direction === 'tab') {
- $cell = this.getRightCell$($cell);
- } else if (direction === 'up') {
- $cell = this.getAboveCell$($cell);
- } else if (direction === 'down') {
- $cell = this.getBelowCell$($cell);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- const focusLastCell = (direction) => {
- if (!this.$focusedCell || this.$editingCell) {
- return false;
- }
-
- let $cell = this.$focusedCell;
- const { rowIndex, colIndex } = $.data($cell);
-
- if (direction === 'left') {
- $cell = this.getLeftMostCell$(rowIndex);
- } else if (direction === 'right') {
- $cell = this.getRightMostCell$(rowIndex);
- } else if (direction === 'up') {
- $cell = this.getTopMostCell$(colIndex);
- } else if (direction === 'down') {
- $cell = this.getBottomMostCell$(colIndex);
- }
-
- this.focusCell($cell);
- return true;
- };
-
- ['left', 'right', 'up', 'down', 'tab', 'shift+tab'].map(
- direction => this.keyboard.on(direction, () => focusCell(direction))
- );
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('ctrl+' + direction, () => focusLastCell(direction))
- );
-
- this.keyboard.on('esc', () => {
- this.deactivateEditing();
- });
-
- if (this.options.enableInlineFilters) {
- this.keyboard.on('ctrl+f', (e) => {
- const $cell = $.closest('.data-table-col', e.target);
- let { colIndex } = $.data($cell);
-
- this.activateFilter(colIndex);
- return true;
- });
- }
- }
-
- bindKeyboardSelection() {
- const getNextSelectionCursor = (direction) => {
- let $selectionCursor = this.getSelectionCursor();
-
- if (direction === 'left') {
- $selectionCursor = this.getLeftCell$($selectionCursor);
- } else if (direction === 'right') {
- $selectionCursor = this.getRightCell$($selectionCursor);
- } else if (direction === 'up') {
- $selectionCursor = this.getAboveCell$($selectionCursor);
- } else if (direction === 'down') {
- $selectionCursor = this.getBelowCell$($selectionCursor);
- }
-
- return $selectionCursor;
- };
-
- ['left', 'right', 'up', 'down'].map(
- direction => this.keyboard.on('shift+' + direction,
- () => this.selectArea(getNextSelectionCursor(direction)))
- );
- }
-
- bindCopyCellContents() {
- this.keyboard.on('ctrl+c', () => {
- this.copyCellContents(this.$focusedCell, this.$selectionCursor);
- });
- }
-
- bindMouseEvents() {
- let mouseDown = null;
-
- $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
- mouseDown = true;
- this.focusCell($(e.delegatedTarget));
- });
-
- $.on(this.bodyScrollable, 'mouseup', () => {
- mouseDown = false;
- });
-
- const selectArea = (e) => {
- if (!mouseDown) return;
- this.selectArea($(e.delegatedTarget));
- };
-
- $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle(selectArea, 50));
- }
-
- focusCell($cell, { skipClearSelection = 0 } = {}) {
- if (!$cell) return;
-
- // don't focus if already editing cell
- if ($cell === this.$editingCell) return;
-
- const { colIndex, isHeader } = $.data($cell);
- if (isHeader) {
- return;
- }
-
- const column = this.columnmanager.getColumn(colIndex);
- if (column.focusable === false) {
- return;
- }
-
- this.scrollToCell($cell);
-
- this.deactivateEditing();
- if (!skipClearSelection) {
- this.clearSelection();
- }
-
- if (this.$focusedCell) {
- this.$focusedCell.classList.remove('selected');
- }
-
- this.$focusedCell = $cell;
- $cell.classList.add('selected');
-
- // so that keyboard nav works
- $cell.focus();
-
- this.highlightRowColumnHeader($cell);
- }
-
- highlightRowColumnHeader($cell) {
- const { colIndex, rowIndex } = $.data($cell);
- const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
- const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
- const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
-
- if (this.lastHeaders) {
- $.removeStyle(this.lastHeaders, 'backgroundColor');
- }
-
- const colHeader = $(colHeaderSelector, this.wrapper);
- const rowHeader = $(rowHeaderSelector, this.wrapper);
-
- $.style([colHeader, rowHeader], {
- backgroundColor: '#f5f7fa' // light-bg
- });
-
- this.lastHeaders = [colHeader, rowHeader];
- }
-
- selectAreaOnClusterChanged() {
- if (!(this.$focusedCell && this.$selectionCursor)) return;
- const { colIndex, rowIndex } = $.data(this.$selectionCursor);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell || $cell === this.$selectionCursor) return;
-
- // selectArea needs $focusedCell
- const fCell = $.data(this.$focusedCell);
- this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
-
- this.selectArea($cell);
- }
-
- focusCellOnClusterChanged() {
- if (!this.$focusedCell) return;
-
- const { colIndex, rowIndex } = $.data(this.$focusedCell);
- const $cell = this.getCell$(colIndex, rowIndex);
-
- if (!$cell) return;
- // this function is called after selectAreaOnClusterChanged,
- // focusCell calls clearSelection which resets the area selection
- // so a flag to skip it
- this.focusCell($cell, { skipClearSelection: 1 });
- }
-
- selectArea($selectionCursor) {
- if (!this.$focusedCell) return;
-
- if (this._selectArea(this.$focusedCell, $selectionCursor)) {
- // valid selection
- this.$selectionCursor = $selectionCursor;
- }
- };
-
- _selectArea($cell1, $cell2) {
- if ($cell1 === $cell2) return false;
-
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return false;
-
- this.clearSelection();
- cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
- return true;
- }
-
- getCellsInRange($cell1, $cell2) {
- let colIndex1, rowIndex1, colIndex2, rowIndex2;
-
- if (typeof $cell1 === 'number') {
- [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
- } else
- if (typeof $cell1 === 'object') {
-
- if (!($cell1 && $cell2)) {
- return false;
- }
-
- const cell1 = $.data($cell1);
- const cell2 = $.data($cell2);
-
- colIndex1 = cell1.colIndex;
- rowIndex1 = cell1.rowIndex;
- colIndex2 = cell2.colIndex;
- rowIndex2 = cell2.rowIndex;
- }
-
- if (rowIndex1 > rowIndex2) {
- [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
- }
-
- if (colIndex1 > colIndex2) {
- [colIndex1, colIndex2] = [colIndex2, colIndex1];
- }
-
- if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
- return false;
- }
-
- let cells = [];
- let colIndex = colIndex1;
- let rowIndex = rowIndex1;
- let rowIndices = [];
-
- while (rowIndex <= rowIndex2) {
- rowIndices.push(rowIndex);
- rowIndex++;
- }
-
- rowIndices.map(rowIndex => {
- while (colIndex <= colIndex2) {
- cells.push([colIndex, rowIndex]);
- colIndex++;
- }
- colIndex = colIndex1;
- });
-
- return cells;
- }
-
- clearSelection() {
- $.each('.data-table-col.highlight', this.bodyScrollable)
- .map(cell => cell.classList.remove('highlight'));
-
- this.$selectionCursor = null;
- }
-
- getSelectionCursor() {
- return this.$selectionCursor || this.$focusedCell;
- }
-
- activateEditing($cell) {
- this.focusCell($cell);
- const { rowIndex, colIndex } = $.data($cell);
-
- const col = this.columnmanager.getColumn(colIndex);
- if (col && (col.editable === false || col.focusable === false)) {
- return;
- }
-
- const cell = this.getCell(colIndex, rowIndex);
- if (cell && cell.editable === false) {
- return;
- }
-
- if (this.$editingCell) {
- const { _rowIndex, _colIndex } = $.data(this.$editingCell);
-
- if (rowIndex === _rowIndex && colIndex === _colIndex) {
- // editing the same cell
- return;
- }
- }
-
- this.$editingCell = $cell;
- $cell.classList.add('editing');
-
- const $editCell = $('.edit-cell', $cell);
- $editCell.innerHTML = '';
-
- const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
-
- if (editor) {
- this.currentCellEditor = editor;
- // initialize editing input with cell value
- editor.initValue(cell.content, rowIndex, col);
- }
- }
-
- deactivateEditing() {
- // keep focus on the cell so that keyboard navigation works
- if (this.$focusedCell) this.$focusedCell.focus();
-
- if (!this.$editingCell) return;
- this.$editingCell.classList.remove('editing');
- this.$editingCell = null;
- }
-
- getEditor(colIndex, rowIndex, value, parent) {
- // debugger;
- const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
- if (obj && obj.setValue) return obj;
-
- // editing fallback
- const $input = $.create('input', {
- class: 'input-style',
- type: 'text',
- inside: parent
- });
-
- return {
- initValue(value) {
- $input.focus();
- $input.value = value;
- },
- getValue() {
- return $input.value;
- },
- setValue(value) {
- $input.value = value;
- }
- };
- }
-
- submitEditing() {
- if (!this.$editingCell) return;
- const $cell = this.$editingCell;
- const { rowIndex, colIndex } = $.data($cell);
- const col = this.datamanager.getColumn(colIndex);
-
- if ($cell) {
- const editor = this.currentCellEditor;
-
- if (editor) {
- const value = editor.getValue();
- const done = editor.setValue(value, rowIndex, col);
- const oldValue = this.getCell(colIndex, rowIndex).content;
-
- // update cell immediately
- this.updateCell(colIndex, rowIndex, value);
+ // so that keyboard nav works
$cell.focus();
- if (done && done.then) {
- // revert to oldValue if promise fails
- done.catch((e) => {
- console.log(e);
- this.updateCell(colIndex, rowIndex, oldValue);
- });
+ this.highlightRowColumnHeader($cell);
+ }
+
+ highlightRowColumnHeader($cell) {
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell);
+ const _colIndex = this.datamanager.getColumnIndexById('_rowIndex');
+ const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
+ const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
+
+ if (this.lastHeaders) {
+ $.removeStyle(this.lastHeaders, 'backgroundColor');
}
- }
+
+ const colHeader = $(colHeaderSelector, this.wrapper);
+ const rowHeader = $(rowHeaderSelector, this.wrapper);
+
+ $.style([colHeader, rowHeader], {
+ backgroundColor: '#f5f7fa' // light-bg
+ });
+
+ this.lastHeaders = [colHeader, rowHeader];
}
- this.currentCellEditor = null;
- }
+ selectAreaOnClusterChanged() {
+ if (!(this.$focusedCell && this.$selectionCursor)) return;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$selectionCursor);
+ const $cell = this.getCell$(colIndex, rowIndex);
- copyCellContents($cell1, $cell2) {
- if (!$cell2 && $cell1) {
- // copy only focusedCell
- const { colIndex, rowIndex } = $.data($cell1);
- const cell = this.getCell(colIndex, rowIndex);
- copyTextToClipboard(cell.content);
- return;
+ if (!$cell || $cell === this.$selectionCursor) return;
+
+ // selectArea needs $focusedCell
+ const fCell = $.data(this.$focusedCell);
+ this.$focusedCell = this.getCell$(fCell.colIndex, fCell.rowIndex);
+
+ this.selectArea($cell);
}
- const cells = this.getCellsInRange($cell1, $cell2);
- if (!cells) return;
+ focusCellOnClusterChanged() {
+ if (!this.$focusedCell) return;
- const values = cells
- // get cell objects
- .map(index => this.getCell(...index))
- // convert to array of rows
- .reduce((acc, curr) => {
- const rowIndex = curr.rowIndex;
+ const {
+ colIndex,
+ rowIndex
+ } = $.data(this.$focusedCell);
+ const $cell = this.getCell$(colIndex, rowIndex);
- acc[rowIndex] = acc[rowIndex] || [];
- acc[rowIndex].push(curr.content);
-
- return acc;
- }, [])
- // join values by tab
- .map(row => row.join('\t'))
- // join rows by newline
- .join('\n');
-
- copyTextToClipboard(values);
- }
-
- activateFilter(colIndex) {
- this.columnmanager.toggleFilter();
- this.columnmanager.focusFilter(colIndex);
-
- if (!this.columnmanager.isFilterShown) {
- // put focus back on cell
- this.$focusedCell.focus();
+ if (!$cell) return;
+ // this function is called after selectAreaOnClusterChanged,
+ // focusCell calls clearSelection which resets the area selection
+ // so a flag to skip it
+ this.focusCell($cell, {
+ skipClearSelection: 1
+ });
}
- }
- updateCell(colIndex, rowIndex, value) {
- const cell = this.datamanager.updateCell(colIndex, rowIndex, {
- content: value
- });
- this.refreshCell(cell);
- }
+ selectArea($selectionCursor) {
+ if (!this.$focusedCell) return;
- refreshCell(cell) {
- const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
- $cell.innerHTML = this.getCellContent(cell);
- }
+ if (this._selectArea(this.$focusedCell, $selectionCursor)) {
+ // valid selection
+ this.$selectionCursor = $selectionCursor;
+ }
+ };
- isStandardCell(colIndex) {
- // Standard cells are in Sr. No and Checkbox column
- return colIndex < this.columnmanager.getFirstColumnIndex();
- }
+ _selectArea($cell1, $cell2) {
+ if ($cell1 === $cell2) return false;
- getCell$(colIndex, rowIndex) {
- return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
- }
+ const cells = this.getCellsInRange($cell1, $cell2);
+ if (!cells) return false;
- getAboveCell$($cell) {
- const { colIndex } = $.data($cell);
- const $aboveRow = $cell.parentElement.previousElementSibling;
+ this.clearSelection();
+ cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
+ return true;
+ }
- return $(`[data-col-index="${colIndex}"]`, $aboveRow);
- }
+ getCellsInRange($cell1, $cell2) {
+ let colIndex1, rowIndex1, colIndex2, rowIndex2;
- getBelowCell$($cell) {
- const { colIndex } = $.data($cell);
- const $belowRow = $cell.parentElement.nextElementSibling;
+ if (typeof $cell1 === 'number') {
+ [colIndex1, rowIndex1, colIndex2, rowIndex2] = arguments;
+ } else
+ if (typeof $cell1 === 'object') {
- return $(`[data-col-index="${colIndex}"]`, $belowRow);
- }
+ if (!($cell1 && $cell2)) {
+ return false;
+ }
- getLeftCell$($cell) {
- return $cell.previousElementSibling;
- }
+ const cell1 = $.data($cell1);
+ const cell2 = $.data($cell2);
- getRightCell$($cell) {
- return $cell.nextElementSibling;
- }
+ colIndex1 = cell1.colIndex;
+ rowIndex1 = cell1.rowIndex;
+ colIndex2 = cell2.colIndex;
+ rowIndex2 = cell2.rowIndex;
+ }
- getLeftMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
- }
+ if (rowIndex1 > rowIndex2) {
+ [rowIndex1, rowIndex2] = [rowIndex2, rowIndex1];
+ }
- getRightMostCell$(rowIndex) {
- return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
- }
+ if (colIndex1 > colIndex2) {
+ [colIndex1, colIndex2] = [colIndex2, colIndex1];
+ }
- getTopMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
- }
+ if (this.isStandardCell(colIndex1) || this.isStandardCell(colIndex2)) {
+ return false;
+ }
- getBottomMostCell$(colIndex) {
- return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
- }
+ let cells = [];
+ let colIndex = colIndex1;
+ let rowIndex = rowIndex1;
+ let rowIndices = [];
- getCell(colIndex, rowIndex) {
- return this.instance.datamanager.getCell(colIndex, rowIndex);
- }
+ while (rowIndex <= rowIndex2) {
+ rowIndices.push(rowIndex);
+ rowIndex++;
+ }
- getCellAttr($cell) {
- return this.instance.getCellAttr($cell);
- }
+ rowIndices.map(rowIndex => {
+ while (colIndex <= colIndex2) {
+ cells.push([colIndex, rowIndex]);
+ colIndex++;
+ }
+ colIndex = colIndex1;
+ });
- getRowHeight() {
- return $.style($('.data-table-row', this.bodyScrollable), 'height');
- }
+ return cells;
+ }
- scrollToCell($cell) {
- if ($.inViewport($cell, this.bodyScrollable)) return false;
+ clearSelection() {
+ $.each('.data-table-col.highlight', this.bodyScrollable)
+ .map(cell => cell.classList.remove('highlight'));
- const { rowIndex } = $.data($cell);
- this.rowmanager.scrollToRow(rowIndex);
- return false;
- }
+ this.$selectionCursor = null;
+ }
- getRowCountPerPage() {
- return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
- }
+ getSelectionCursor() {
+ return this.$selectionCursor || this.$focusedCell;
+ }
- getCellHTML(cell) {
- const { rowIndex, colIndex, isHeader, isFilter } = cell;
- const dataAttr = makeDataAttributeString({
- rowIndex,
- colIndex,
- isHeader,
- isFilter
- });
+ activateEditing($cell) {
+ this.focusCell($cell);
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
- return `
+ const col = this.columnmanager.getColumn(colIndex);
+ if (col && (col.editable === false || col.focusable === false)) {
+ return;
+ }
+
+ const cell = this.getCell(colIndex, rowIndex);
+ if (cell && cell.editable === false) {
+ return;
+ }
+
+ if (this.$editingCell) {
+ const {
+ _rowIndex,
+ _colIndex
+ } = $.data(this.$editingCell);
+
+ if (rowIndex === _rowIndex && colIndex === _colIndex) {
+ // editing the same cell
+ return;
+ }
+ }
+
+ this.$editingCell = $cell;
+ $cell.classList.add('editing');
+
+ const $editCell = $('.edit-cell', $cell);
+ $editCell.innerHTML = '';
+
+ const editor = this.getEditor(colIndex, rowIndex, cell.content, $editCell);
+
+ if (editor) {
+ this.currentCellEditor = editor;
+ // initialize editing input with cell value
+ editor.initValue(cell.content, rowIndex, col);
+ }
+ }
+
+ deactivateEditing() {
+ // keep focus on the cell so that keyboard navigation works
+ if (this.$focusedCell) this.$focusedCell.focus();
+
+ if (!this.$editingCell) return;
+ this.$editingCell.classList.remove('editing');
+ this.$editingCell = null;
+ }
+
+ getEditor(colIndex, rowIndex, value, parent) {
+ // debugger;
+ const obj = this.options.getEditor(colIndex, rowIndex, value, parent);
+ if (obj && obj.setValue) return obj;
+
+ // editing fallback
+ const $input = $.create('input', {
+ class: 'input-style',
+ type: 'text',
+ inside: parent
+ });
+
+ return {
+ initValue(value) {
+ $input.focus();
+ $input.value = value;
+ },
+ getValue() {
+ return $input.value;
+ },
+ setValue(value) {
+ $input.value = value;
+ }
+ };
+ }
+
+ submitEditing() {
+ if (!this.$editingCell) return;
+ const $cell = this.$editingCell;
+ const {
+ rowIndex,
+ colIndex
+ } = $.data($cell);
+ const col = this.datamanager.getColumn(colIndex);
+
+ if ($cell) {
+ const editor = this.currentCellEditor;
+
+ if (editor) {
+ const value = editor.getValue();
+ const done = editor.setValue(value, rowIndex, col);
+ const oldValue = this.getCell(colIndex, rowIndex).content;
+
+ // update cell immediately
+ this.updateCell(colIndex, rowIndex, value);
+ $cell.focus();
+
+ if (done && done.then) {
+ // revert to oldValue if promise fails
+ done.catch((e) => {
+ console.log(e);
+ this.updateCell(colIndex, rowIndex, oldValue);
+ });
+ }
+ }
+ }
+
+ this.currentCellEditor = null;
+ }
+
+ copyCellContents($cell1, $cell2) {
+ if (!$cell2 && $cell1) {
+ // copy only focusedCell
+ const {
+ colIndex,
+ rowIndex
+ } = $.data($cell1);
+ const cell = this.getCell(colIndex, rowIndex);
+ copyTextToClipboard(cell.content);
+ return;
+ }
+ const cells = this.getCellsInRange($cell1, $cell2);
+
+ if (!cells) return;
+
+ const values = cells
+ // get cell objects
+ .map(index => this.getCell(...index))
+ // convert to array of rows
+ .reduce((acc, curr) => {
+ const rowIndex = curr.rowIndex;
+
+ acc[rowIndex] = acc[rowIndex] || [];
+ acc[rowIndex].push(curr.content);
+
+ return acc;
+ }, [])
+ // join values by tab
+ .map(row => row.join('\t'))
+ // join rows by newline
+ .join('\n');
+
+ copyTextToClipboard(values);
+ }
+
+ activateFilter(colIndex) {
+ this.columnmanager.toggleFilter();
+ this.columnmanager.focusFilter(colIndex);
+
+ if (!this.columnmanager.isFilterShown) {
+ // put focus back on cell
+ this.$focusedCell.focus();
+ }
+ }
+
+ updateCell(colIndex, rowIndex, value) {
+ const cell = this.datamanager.updateCell(colIndex, rowIndex, {
+ content: value
+ });
+ this.refreshCell(cell);
+ }
+
+ refreshCell(cell) {
+ const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable);
+ $cell.innerHTML = this.getCellContent(cell);
+ }
+
+ isStandardCell(colIndex) {
+ // Standard cells are in Sr. No and Checkbox column
+ return colIndex < this.columnmanager.getFirstColumnIndex();
+ }
+
+ getCell$(colIndex, rowIndex) {
+ return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable);
+ }
+
+ getAboveCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $aboveRow = $cell.parentElement.previousElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $aboveRow);
+ }
+
+ getBelowCell$($cell) {
+ const {
+ colIndex
+ } = $.data($cell);
+ const $belowRow = $cell.parentElement.nextElementSibling;
+
+ return $(`[data-col-index="${colIndex}"]`, $belowRow);
+ }
+
+ getLeftCell$($cell) {
+ return $cell.previousElementSibling;
+ }
+
+ getRightCell$($cell) {
+ return $cell.nextElementSibling;
+ }
+
+ getLeftMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getFirstColumnIndex(), rowIndex);
+ }
+
+ getRightMostCell$(rowIndex) {
+ return this.getCell$(this.columnmanager.getLastColumnIndex(), rowIndex);
+ }
+
+ getTopMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getFirstRowIndex());
+ }
+
+ getBottomMostCell$(colIndex) {
+ return this.getCell$(colIndex, this.rowmanager.getLastRowIndex());
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.instance.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getCellAttr($cell) {
+ return this.instance.getCellAttr($cell);
+ }
+
+ getRowHeight() {
+ return $.style($('.data-table-row', this.bodyScrollable), 'height');
+ }
+
+ scrollToCell($cell) {
+ if ($.inViewport($cell, this.bodyScrollable)) return false;
+
+ const {
+ rowIndex
+ } = $.data($cell);
+ this.rowmanager.scrollToRow(rowIndex);
+ return false;
+ }
+
+ getRowCountPerPage() {
+ return Math.ceil(this.instance.getViewportHeight() / this.getRowHeight());
+ }
+
+ getCellHTML(cell) {
+ const {
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ } = cell;
+ const dataAttr = makeDataAttributeString({
+ rowIndex,
+ colIndex,
+ isHeader,
+ isFilter
+ });
+
+ return `
${this.getCellContent(cell)}
|
`;
- }
-
- getCellContent(cell) {
- const { isHeader } = cell;
-
- const editable = !isHeader && cell.editable !== false;
- const editCellHTML = editable ? this.getEditCellHTML() : '';
-
- const sortable = isHeader && cell.sortable !== false;
- const sortIndicator = sortable ? '' : '';
-
- const resizable = isHeader && cell.resizable !== false;
- const resizeColumn = resizable ? '' : '';
-
- const hasDropdown = isHeader && cell.dropdown !== false;
- const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
-
- let contentHTML;
- if (cell.isHeader || cell.isFilter || !cell.column.format) {
- contentHTML = cell.content;
- } else {
- contentHTML = cell.column.format(cell.content, cell);
}
- return `
+ getCellContent(cell) {
+ const {
+ isHeader
+ } = cell;
+
+ const editable = !isHeader && cell.editable !== false;
+ const editCellHTML = editable ? this.getEditCellHTML() : '';
+
+ const sortable = isHeader && cell.sortable !== false;
+ const sortIndicator = sortable ? '' : '';
+
+ const resizable = isHeader && cell.resizable !== false;
+ const resizeColumn = resizable ? '' : '';
+
+ const hasDropdown = isHeader && cell.dropdown !== false;
+ const dropdown = hasDropdown ? `${getDropdownHTML()}
` : '';
+
+ let contentHTML;
+ if (cell.isHeader || cell.isFilter || !cell.column.format) {
+ contentHTML = cell.content;
+ } else {
+ contentHTML = cell.column.format(cell.content, cell);
+ }
+
+ return `
${(contentHTML)}
${sortIndicator}
@@ -612,15 +660,15 @@ export default class CellManager {
${editCellHTML}
`;
- }
+ }
- getEditCellHTML() {
- return `
+ getEditCellHTML() {
+ return `
`;
- }
+ }
- cellSelector(colIndex, rowIndex) {
- return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
- }
+ cellSelector(colIndex, rowIndex) {
+ return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`;
+ }
}
diff --git a/src/columnmanager.js b/src/columnmanager.js
index e67951d..6f4191a 100644
--- a/src/columnmanager.js
+++ b/src/columnmanager.js
@@ -1,413 +1,452 @@
import $ from './dom';
import Sortable from 'sortablejs';
-import { getDefault, linkProperties, debounce } from './utils';
+import {
+ getDefault,
+ linkProperties,
+ debounce
+} from './utils';
export default class ColumnManager {
- constructor(instance) {
- this.instance = instance;
+ constructor(instance) {
+ this.instance = instance;
- linkProperties(this, this.instance, [
- 'options',
- 'fireEvent',
- 'header',
- 'datamanager',
- 'style',
- 'wrapper',
- 'rowmanager',
- 'bodyScrollable'
- ]);
+ linkProperties(this, this.instance, [
+ 'options',
+ 'fireEvent',
+ 'header',
+ 'datamanager',
+ 'style',
+ 'wrapper',
+ 'rowmanager',
+ 'bodyScrollable'
+ ]);
- this.bindEvents();
- getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
- }
+ this.bindEvents();
+ getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
+ }
- renderHeader() {
- this.header.innerHTML = '';
- this.refreshHeader();
- }
+ renderHeader() {
+ this.header.innerHTML = '';
+ this.refreshHeader();
+ }
- refreshHeader() {
- const columns = this.datamanager.getColumns();
+ refreshHeader() {
+ const columns = this.datamanager.getColumns();
- if (!$('.data-table-col', this.header)) {
- // insert html
+ if (!$('.data-table-col', this.header)) {
+ // insert html
- let html = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- if (this.options.enableInlineFilters) {
- html += this.rowmanager.getRowHTML(columns, { isFilter: 1 });
- }
+ let html = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ if (this.options.enableInlineFilters) {
+ html += this.rowmanager.getRowHTML(columns, {
+ isFilter: 1
+ });
+ }
- $('thead', this.header).innerHTML = html;
+ $('thead', this.header).innerHTML = html;
- this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
+ this.$filterRow = $('.data-table-row[data-is-filter]', this.header);
- if (this.$filterRow) {
- // hide filter row immediately, so it doesn't disturb layout
- $.style(this.$filterRow, {
- display: 'none'
- });
- }
- } else {
- // refresh dom state
- const $cols = $.each('.data-table-col', this.header);
- if (columns.length < $cols.length) {
- // deleted column
- $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
- return;
- }
+ if (this.$filterRow) {
+ // hide filter row immediately, so it doesn't disturb layout
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ }
+ } else {
+ // refresh dom state
+ const $cols = $.each('.data-table-col', this.header);
+ if (columns.length < $cols.length) {
+ // deleted column
+ $('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, {
+ isHeader: 1
+ });
+ return;
+ }
- $cols.map(($col, i) => {
- const column = columns[i];
- // column sorted or order changed
- // update colIndex of each header cell
- $.data($col, {
- colIndex: column.colIndex
- });
+ $cols.map(($col, i) => {
+ const column = columns[i];
+ // column sorted or order changed
+ // update colIndex of each header cell
+ $.data($col, {
+ colIndex: column.colIndex
+ });
- // refresh sort indicator
- const sortIndicator = $('.sort-indicator', $col);
- if (sortIndicator) {
- sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ // refresh sort indicator
+ const sortIndicator = $('.sort-indicator', $col);
+ if (sortIndicator) {
+ sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
+ }
+ });
}
- });
+ // reset columnMap
+ this.$columnMap = [];
}
- // reset columnMap
- this.$columnMap = [];
- }
- bindEvents() {
- this.bindDropdown();
- this.bindResizeColumn();
- this.bindMoveColumn();
- this.bindFilter();
- }
-
- bindDropdown() {
- let $activeDropdown;
- $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
- const $dropdown = $.closest('.data-table-dropdown', $button);
-
- if (!$dropdown.classList.contains('is-active')) {
- deactivateDropdown();
- $dropdown.classList.add('is-active');
- $activeDropdown = $dropdown;
- } else {
- deactivateDropdown();
- }
- });
-
- $.on(document.body, 'click', (e) => {
- if (e.target.matches('.data-table-dropdown-toggle')) return;
- deactivateDropdown();
- });
-
- const dropdownItems = this.options.headerDropdown;
-
- $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
- const $col = $.closest('.data-table-col', $item);
- const { index } = $.data($item);
- const { colIndex } = $.data($col);
- let callback = dropdownItems[index].action;
-
- callback && callback.call(this.instance, this.getColumn(colIndex));
- });
-
- function deactivateDropdown(e) {
- $activeDropdown && $activeDropdown.classList.remove('is-active');
- $activeDropdown = null;
+ bindEvents() {
+ this.bindDropdown();
+ this.bindResizeColumn();
+ this.bindMoveColumn();
+ this.bindFilter();
}
- }
- bindResizeColumn() {
- let isDragging = false;
- let $resizingCell, startWidth, startX;
+ bindDropdown() {
+ let $activeDropdown;
+ $.on(this.header, 'click', '.data-table-dropdown-toggle', (e, $button) => {
+ const $dropdown = $.closest('.data-table-dropdown', $button);
- $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
- document.body.classList.add('data-table-resize');
- const $cell = $handle.parentNode.parentNode;
- $resizingCell = $cell;
- const { colIndex } = $.data($resizingCell);
- const col = this.getColumn(colIndex);
-
- if (col && col.resizable === false) {
- return;
- }
-
- isDragging = true;
- startWidth = $.style($('.content', $resizingCell), 'width');
- startX = e.pageX;
- });
-
- $.on(document.body, 'mouseup', (e) => {
- document.body.classList.remove('data-table-resize');
- if (!$resizingCell) return;
- isDragging = false;
-
- const { colIndex } = $.data($resizingCell);
- this.setColumnWidth(colIndex);
- this.style.setBodyStyle();
- $resizingCell = null;
- });
-
- $.on(document.body, 'mousemove', (e) => {
- if (!isDragging) return;
- const finalWidth = startWidth + (e.pageX - startX);
- const { colIndex } = $.data($resizingCell);
-
- if (this.getColumnMinWidth(colIndex) > finalWidth) {
- // don't resize past minWidth
- return;
- }
- this.datamanager.updateColumn(colIndex, { width: finalWidth });
- this.setColumnHeaderWidth(colIndex);
- });
- }
-
- bindMoveColumn() {
- let initialized;
-
- const initialize = () => {
- if (initialized) {
- $.off(document.body, 'mousemove', initialize);
- return;
- }
- const ready = $('.data-table-col', this.header);
- if (!ready) return;
-
- const $parent = $('.data-table-row', this.header);
-
- this.sortable = Sortable.create($parent, {
- onEnd: (e) => {
- const { oldIndex, newIndex } = e;
- const $draggedCell = e.item;
- const { colIndex } = $.data($draggedCell);
- if (+colIndex === newIndex) return;
-
- this.switchColumn(oldIndex, newIndex);
- },
- preventOnFilter: false,
- filter: '.column-resizer, .data-table-dropdown',
- animation: 150
- });
- };
-
- $.on(document.body, 'mousemove', initialize);
- }
-
- bindSortColumn() {
-
- $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
- const $cell = span.closest('.data-table-col');
- let { colIndex, sortOrder } = $.data($cell);
- sortOrder = getDefault(sortOrder, 'none');
- const col = this.getColumn(colIndex);
-
- if (col && col.sortable === false) {
- return;
- }
-
- // reset sort indicator
- $('.sort-indicator', this.header).textContent = '';
- $.each('.data-table-col', this.header).map($cell => {
- $.data($cell, {
- sortOrder: 'none'
+ if (!$dropdown.classList.contains('is-active')) {
+ deactivateDropdown();
+ $dropdown.classList.add('is-active');
+ $activeDropdown = $dropdown;
+ } else {
+ deactivateDropdown();
+ }
});
- });
- let nextSortOrder, textContent;
- if (sortOrder === 'none') {
- nextSortOrder = 'asc';
- textContent = '▲';
- } else if (sortOrder === 'asc') {
- nextSortOrder = 'desc';
- textContent = '▼';
- } else if (sortOrder === 'desc') {
- nextSortOrder = 'none';
- textContent = '';
- }
-
- $.data($cell, {
- sortOrder: nextSortOrder
- });
- $('.sort-indicator', $cell).textContent = textContent;
-
- this.sortColumn(colIndex, nextSortOrder);
- });
- }
-
- sortColumn(colIndex, nextSortOrder) {
- this.instance.freeze();
- this.sortRows(colIndex, nextSortOrder)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onSortColumn', this.getColumn(colIndex));
- });
- }
-
- removeColumn(colIndex) {
- const removedCol = this.getColumn(colIndex);
- this.instance.freeze();
- this.datamanager.removeColumn(colIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => this.instance.unfreeze())
- .then(() => {
- this.fireEvent('onRemoveColumn', removedCol);
- });
- }
-
- switchColumn(oldIndex, newIndex) {
- this.instance.freeze();
- this.datamanager.switchColumn(oldIndex, newIndex)
- .then(() => {
- this.refreshHeader();
- return this.rowmanager.refreshRows();
- })
- .then(() => {
- this.setColumnWidth(oldIndex);
- this.setColumnWidth(newIndex);
- this.instance.unfreeze();
- })
- .then(() => {
- this.fireEvent('onSwitchColumn',
- this.getColumn(oldIndex), this.getColumn(newIndex)
- );
- });
- }
-
- toggleFilter() {
- this.isFilterShown = this.isFilterShown || false;
-
- if (this.isFilterShown) {
- $.style(this.$filterRow, {
- display: 'none'
- });
- } else {
- $.style(this.$filterRow, {
- display: ''
- });
- }
-
- this.isFilterShown = !this.isFilterShown;
- this.style.setBodyStyle();
- }
-
- focusFilter(colIndex) {
- if (!this.isFilterShown) return;
-
- const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
- $filterInput.focus();
- }
-
- bindFilter() {
- if (!this.options.enableInlineFilters) return;
- const handler = e => {
- const $filterCell = $.closest('.data-table-col', e.target);
- const { colIndex } = $.data($filterCell);
- const keyword = e.target.value;
-
- this.datamanager.filterRows(keyword, colIndex)
- .then(({ rowsToHide, rowsToShow }) => {
- rowsToHide.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.add('hide');
- });
- rowsToShow.map(rowIndex => {
- const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- $tr.classList.remove('hide');
- });
+ $.on(document.body, 'click', (e) => {
+ if (e.target.matches('.data-table-dropdown-toggle')) return;
+ deactivateDropdown();
});
- };
- $.on(this.header, 'keydown', '.data-table-filter', debounce(handler, 300));
- }
- sortRows(colIndex, sortOrder) {
- return this.datamanager.sortRows(colIndex, sortOrder);
- }
+ const dropdownItems = this.options.headerDropdown;
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
+ $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
+ const $col = $.closest('.data-table-col', $item);
+ const {
+ index
+ } = $.data($item);
+ const {
+ colIndex
+ } = $.data($col);
+ let callback = dropdownItems[index].action;
- getColumns() {
- return this.datamanager.getColumns();
- }
+ callback && callback.call(this.instance, this.getColumn(colIndex));
+ });
- setColumnWidth(colIndex) {
- colIndex = +colIndex;
- this._columnWidthMap = this._columnWidthMap || [];
-
- const { width } = this.getColumn(colIndex);
-
- let index = this._columnWidthMap[colIndex];
- const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
- const styles = {
- width: width + 'px'
- };
-
- index = this.style.setStyle(selector, styles, index);
- this._columnWidthMap[colIndex] = index;
- }
-
- setColumnHeaderWidth(colIndex) {
- colIndex = +colIndex;
- this.$columnMap = this.$columnMap || [];
- const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
- const { width } = this.getColumn(colIndex);
-
- let $column = this.$columnMap[colIndex];
- if (!$column) {
- $column = this.header.querySelector(selector);
- this.$columnMap[colIndex] = $column;
+ function deactivateDropdown(e) {
+ $activeDropdown && $activeDropdown.classList.remove('is-active');
+ $activeDropdown = null;
+ }
}
- $column.style.width = width + 'px';
- }
+ bindResizeColumn() {
+ let isDragging = false;
+ let $resizingCell, startWidth, startX;
- getColumnMinWidth(colIndex) {
- colIndex = +colIndex;
- return this.getColumn(colIndex).minWidth || 24;
- }
+ $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
+ document.body.classList.add('data-table-resize');
+ const $cell = $handle.parentNode.parentNode;
+ $resizingCell = $cell;
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ const col = this.getColumn(colIndex);
- getFirstColumnIndex() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ if (col && col.resizable === false) {
+ return;
+ }
+
+ isDragging = true;
+ startWidth = $.style($('.content', $resizingCell), 'width');
+ startX = e.pageX;
+ });
+
+ $.on(document.body, 'mouseup', (e) => {
+ document.body.classList.remove('data-table-resize');
+ if (!$resizingCell) return;
+ isDragging = false;
+
+ const {
+ colIndex
+ } = $.data($resizingCell);
+ this.setColumnWidth(colIndex);
+ this.style.setBodyStyle();
+ $resizingCell = null;
+ });
+
+ $.on(document.body, 'mousemove', (e) => {
+ if (!isDragging) return;
+ const finalWidth = startWidth + (e.pageX - startX);
+ const {
+ colIndex
+ } = $.data($resizingCell);
+
+ if (this.getColumnMinWidth(colIndex) > finalWidth) {
+ // don't resize past minWidth
+ return;
+ }
+ this.datamanager.updateColumn(colIndex, {
+ width: finalWidth
+ });
+ this.setColumnHeaderWidth(colIndex);
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ bindMoveColumn() {
+ let initialized;
+
+ const initialize = () => {
+ if (initialized) {
+ $.off(document.body, 'mousemove', initialize);
+ return;
+ }
+ const ready = $('.data-table-col', this.header);
+ if (!ready) return;
+
+ const $parent = $('.data-table-row', this.header);
+
+ this.sortable = Sortable.create($parent, {
+ onEnd: (e) => {
+ const {
+ oldIndex,
+ newIndex
+ } = e;
+ const $draggedCell = e.item;
+ const {
+ colIndex
+ } = $.data($draggedCell);
+ if (+colIndex === newIndex) return;
+
+ this.switchColumn(oldIndex, newIndex);
+ },
+ preventOnFilter: false,
+ filter: '.column-resizer, .data-table-dropdown',
+ animation: 150
+ });
+ };
+
+ $.on(document.body, 'mousemove', initialize);
}
- return 0;
- }
+ bindSortColumn() {
- getHeaderCell$(colIndex) {
- return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
- }
+ $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
+ const $cell = span.closest('.data-table-col');
+ let {
+ colIndex,
+ sortOrder
+ } = $.data($cell);
+ sortOrder = getDefault(sortOrder, 'none');
+ const col = this.getColumn(colIndex);
- getLastColumnIndex() {
- return this.datamanager.getColumnCount() - 1;
- }
+ if (col && col.sortable === false) {
+ return;
+ }
- getSerialColumnIndex() {
- const columns = this.datamanager.getColumns();
+ // reset sort indicator
+ $('.sort-indicator', this.header).textContent = '';
+ $.each('.data-table-col', this.header).map($cell => {
+ $.data($cell, {
+ sortOrder: 'none'
+ });
+ });
- return columns.findIndex(column => column.content.includes('Sr. No'));
- }
+ let nextSortOrder, textContent;
+ if (sortOrder === 'none') {
+ nextSortOrder = 'asc';
+ textContent = '▲';
+ } else if (sortOrder === 'asc') {
+ nextSortOrder = 'desc';
+ textContent = '▼';
+ } else if (sortOrder === 'desc') {
+ nextSortOrder = 'none';
+ textContent = '';
+ }
+
+ $.data($cell, {
+ sortOrder: nextSortOrder
+ });
+ $('.sort-indicator', $cell).textContent = textContent;
+
+ this.sortColumn(colIndex, nextSortOrder);
+ });
+ }
+
+ sortColumn(colIndex, nextSortOrder) {
+ this.instance.freeze();
+ this.sortRows(colIndex, nextSortOrder)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onSortColumn', this.getColumn(colIndex));
+ });
+ }
+
+ removeColumn(colIndex) {
+ const removedCol = this.getColumn(colIndex);
+ this.instance.freeze();
+ this.datamanager.removeColumn(colIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => this.instance.unfreeze())
+ .then(() => {
+ this.fireEvent('onRemoveColumn', removedCol);
+ });
+ }
+
+ switchColumn(oldIndex, newIndex) {
+ this.instance.freeze();
+ this.datamanager.switchColumn(oldIndex, newIndex)
+ .then(() => {
+ this.refreshHeader();
+ return this.rowmanager.refreshRows();
+ })
+ .then(() => {
+ this.setColumnWidth(oldIndex);
+ this.setColumnWidth(newIndex);
+ this.instance.unfreeze();
+ })
+ .then(() => {
+ this.fireEvent('onSwitchColumn',
+ this.getColumn(oldIndex), this.getColumn(newIndex)
+ );
+ });
+ }
+
+ toggleFilter() {
+ this.isFilterShown = this.isFilterShown || false;
+
+ if (this.isFilterShown) {
+ $.style(this.$filterRow, {
+ display: 'none'
+ });
+ } else {
+ $.style(this.$filterRow, {
+ display: ''
+ });
+ }
+
+ this.isFilterShown = !this.isFilterShown;
+ this.style.setBodyStyle();
+ }
+
+ focusFilter(colIndex) {
+ if (!this.isFilterShown) return;
+
+ const $filterInput = $(`[data-col-index="${colIndex}"] .data-table-filter`, this.$filterRow);
+ $filterInput.focus();
+ }
+
+ bindFilter() {
+ if (!this.options.enableInlineFilters) return;
+ const handler = e => {
+ const $filterCell = $.closest('.data-table-col', e.target);
+ const {
+ colIndex
+ } = $.data($filterCell);
+ const keyword = e.target.value;
+
+ this.datamanager.filterRows(keyword, colIndex)
+ .then(({
+ rowsToHide,
+ rowsToShow
+ }) => {
+ rowsToHide.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.add('hide');
+ });
+ rowsToShow.map(rowIndex => {
+ const $tr = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ $tr.classList.remove('hide');
+ });
+ });
+ };
+ $.on(this.header, 'keydown', '.data-table-filter', debounce(handler, 300));
+ }
+
+ sortRows(colIndex, sortOrder) {
+ return this.datamanager.sortRows(colIndex, sortOrder);
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ setColumnWidth(colIndex) {
+ colIndex = +colIndex;
+ this._columnWidthMap = this._columnWidthMap || [];
+
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let index = this._columnWidthMap[colIndex];
+ const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
+ const styles = {
+ width: width + 'px'
+ };
+
+ index = this.style.setStyle(selector, styles, index);
+ this._columnWidthMap[colIndex] = index;
+ }
+
+ setColumnHeaderWidth(colIndex) {
+ colIndex = +colIndex;
+ this.$columnMap = this.$columnMap || [];
+ const selector = `.data-table-header [data-col-index="${colIndex}"] .content`;
+ const {
+ width
+ } = this.getColumn(colIndex);
+
+ let $column = this.$columnMap[colIndex];
+ if (!$column) {
+ $column = this.header.querySelector(selector);
+ this.$columnMap[colIndex] = $column;
+ }
+
+ $column.style.width = width + 'px';
+ }
+
+ getColumnMinWidth(colIndex) {
+ colIndex = +colIndex;
+ return this.getColumn(colIndex).minWidth || 24;
+ }
+
+ getFirstColumnIndex() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getHeaderCell$(colIndex) {
+ return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
+ }
+
+ getLastColumnIndex() {
+ return this.datamanager.getColumnCount() - 1;
+ }
+
+ getSerialColumnIndex() {
+ const columns = this.datamanager.getColumns();
+
+ return columns.findIndex(column => column.content.includes('Sr. No'));
+ }
}
// eslint-disable-next-line
var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
- // add dropdown buttons
- const dropdownItems = this.options.headerDropdown;
+ // add dropdown buttons
+ const dropdownItems = this.options.headerDropdown;
- return `${dropdownButton}
+ return `${dropdownButton}
${dropdownItems.map((d, i) => `
${d.label}
`).join('')}
@@ -415,5 +454,5 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
};
export {
- getDropdownHTML
+ getDropdownHTML
};
diff --git a/src/datamanager.js b/src/datamanager.js
index 4c6993e..09ee9cc 100644
--- a/src/datamanager.js
+++ b/src/datamanager.js
@@ -1,500 +1,518 @@
-import { isNumeric, promisify } from './utils';
+import {
+ isNumeric,
+ promisify
+} from './utils';
export default class DataManager {
- constructor(options) {
- this.options = options;
- this.sortRows = promisify(this.sortRows, this);
- this.switchColumn = promisify(this.switchColumn, this);
- this.removeColumn = promisify(this.removeColumn, this);
- this.filterRows = promisify(this.filterRows, this);
- }
-
- init(data) {
- if (!data) {
- data = this.options.data;
+ constructor(options) {
+ this.options = options;
+ this.sortRows = promisify(this.sortRows, this);
+ this.switchColumn = promisify(this.switchColumn, this);
+ this.removeColumn = promisify(this.removeColumn, this);
+ this.filterRows = promisify(this.filterRows, this);
}
- this.data = data;
-
- this.rowCount = 0;
- this.columns = [];
- this.rows = [];
-
- this.prepareColumns();
- this.prepareRows();
-
- this.prepareNumericColumns();
- }
-
- // computed property
- get currentSort() {
- const col = this.columns.find(col => col.sortOrder !== 'none');
- return col || {
- colIndex: -1,
- sortOrder: 'none'
- };
- }
-
- prepareColumns() {
- this.columns = [];
- this.validateColumns();
- this.prepareDefaultColumns();
- this.prepareHeader();
- }
-
- prepareDefaultColumns() {
- if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
- const cell = {
- id: '_checkbox',
- content: this.getCheckboxHTML(),
- editable: false,
- resizable: false,
- sortable: false,
- focusable: false,
- dropdown: false,
- width: 25
- };
- this.columns.push(cell);
- }
-
- if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
- let cell = {
- id: '_rowIndex',
- content: '',
- align: 'center',
- editable: false,
- resizable: false,
- focusable: false,
- dropdown: false
- };
-
- this.columns.push(cell);
- }
- }
-
- prepareRow(row, i) {
- const baseRowCell = {
- rowIndex: i
- };
-
- return row
- .map((cell, i) => this.prepareCell(cell, i))
- .map(cell => Object.assign({}, baseRowCell, cell));
- }
-
- prepareHeader() {
- let columns = this.columns.concat(this.options.columns);
- const baseCell = {
- isHeader: 1,
- editable: true,
- sortable: true,
- resizable: true,
- focusable: true,
- dropdown: true,
- width: null,
- format: (value) => {
- if (value === null || value === undefined) {
- return '';
- }
- return value + '';
- }
- };
-
- this.columns = columns
- .map((cell, i) => this.prepareCell(cell, i))
- .map(col => Object.assign({}, baseCell, col))
- .map(col => {
- col.id = col.id || col.content;
- return col;
- });
- }
-
- prepareCell(content, i) {
- const cell = {
- content: '',
- align: 'left',
- sortOrder: 'none',
- colIndex: i,
- column: this.columns[i]
- };
-
- if (content !== null && typeof content === 'object') {
- // passed as column/header
- Object.assign(cell, content);
- } else {
- cell.content = content;
- }
-
- return cell;
- }
-
- prepareNumericColumns() {
- const row0 = this.getRow(0);
- if (!row0) return;
- this.columns = this.columns.map((column, i) => {
-
- const cellValue = row0[i].content;
- if (!column.align && cellValue && isNumeric(cellValue)) {
- column.align = 'right';
- }
-
- return column;
- });
- }
-
- prepareRows() {
- this.validateData(this.data);
-
- this.rows = this.data.map((d, i) => {
- const index = this._getNextRowCount();
-
- let row = [];
-
- if (Array.isArray(d)) {
- // row is an array
- if (this.options.addCheckboxColumn) {
- row.push(this.getCheckboxHTML());
- }
- if (this.options.addSerialNoColumn) {
- row.push((index + 1) + '');
- }
- row = row.concat(d);
-
- while (row.length < this.columns.length) {
- row.push('');
+ init(data) {
+ if (!data) {
+ data = this.options.data;
}
- } else {
- // row is a dict
- for (let col of this.columns) {
- if (col.id === '_checkbox') {
- row.push(this.getCheckboxHTML());
- } else if (col.id === '_rowIndex') {
- row.push((index + 1) + '');
- } else {
- row.push(d[col.id]);
- }
+ this.data = data;
+
+ this.rowCount = 0;
+ this.columns = [];
+ this.rows = [];
+
+ this.prepareColumns();
+ this.prepareRows();
+
+ this.prepareNumericColumns();
+ }
+
+ // computed property
+ get currentSort() {
+ const col = this.columns.find(col => col.sortOrder !== 'none');
+ return col || {
+ colIndex: -1,
+ sortOrder: 'none'
+ };
+ }
+
+ prepareColumns() {
+ this.columns = [];
+ this.validateColumns();
+ this.prepareDefaultColumns();
+ this.prepareHeader();
+ }
+
+ prepareDefaultColumns() {
+ if (this.options.addCheckboxColumn && !this.hasColumnById('_checkbox')) {
+ const cell = {
+ id: '_checkbox',
+ content: this.getCheckboxHTML(),
+ editable: false,
+ resizable: false,
+ sortable: false,
+ focusable: false,
+ dropdown: false,
+ width: 25
+ };
+ this.columns.push(cell);
}
- }
- return this.prepareRow(row, index);
- });
- }
+ if (this.options.addSerialNoColumn && !this.hasColumnById('_rowIndex')) {
+ let cell = {
+ id: '_rowIndex',
+ content: '',
+ align: 'center',
+ editable: false,
+ resizable: false,
+ focusable: false,
+ dropdown: false
+ };
- validateColumns() {
- const columns = this.options.columns;
- if (!Array.isArray(columns)) {
- throw new DataError('`columns` must be an array');
+ this.columns.push(cell);
+ }
}
- columns.forEach((column, i) => {
- if (typeof column !== 'string' && typeof column !== 'object') {
- throw new DataError(`column "${i}" must be a string or an object`);
- }
- });
- }
+ prepareHeader() {
+ let columns = this.columns.concat(this.options.columns);
+ const baseCell = {
+ isHeader: 1,
+ editable: true,
+ sortable: true,
+ resizable: true,
+ focusable: true,
+ dropdown: true,
+ width: null,
+ format: (value) => {
+ if (value === null || value === undefined) {
+ return '';
+ }
+ return value + '';
+ }
+ };
- validateData(data) {
- if (Array.isArray(data) &&
- (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
- return true;
+ this.columns = columns
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(col => Object.assign({}, baseCell, col))
+ .map(col => {
+ col.id = col.id || col.content;
+ return col;
+ });
}
- throw new DataError('`data` must be an array of arrays or objects');
- }
- appendRows(rows) {
- this.validateData(rows);
+ prepareCell(content, i) {
+ const cell = {
+ content: '',
+ align: 'left',
+ sortOrder: 'none',
+ colIndex: i,
+ column: this.columns[i]
+ };
- this.rows = this.rows.concat(this.prepareRows(rows));
- }
-
- sortRows(colIndex, sortOrder = 'none') {
- colIndex = +colIndex;
-
- // reset sortOrder and update for colIndex
- this.getColumns()
- .map(col => {
- if (col.colIndex === colIndex) {
- col.sortOrder = sortOrder;
+ if (content !== null && typeof content === 'object') {
+ // passed as column/header
+ Object.assign(cell, content);
} else {
- col.sortOrder = 'none';
+ cell.content = content;
}
- });
- this._sortRows(colIndex, sortOrder);
- }
-
- _sortRows(colIndex, sortOrder) {
-
- if (this.currentSort.colIndex === colIndex) {
- // reverse the array if only sortOrder changed
- if (
- (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
- (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
- ) {
- this.reverseArray(this.rows);
- this.currentSort.sortOrder = sortOrder;
- return;
- }
+ return cell;
}
- 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;
+ prepareNumericColumns() {
+ const row0 = this.getRow(0);
+ if (!row0) return;
+ this.columns = this.columns.map((column, i) => {
- if (sortOrder === 'none') {
- return _aIndex - _bIndex;
- } else if (sortOrder === 'asc') {
- if (_a < _b) return -1;
- if (_a > _b) return 1;
- if (_a === _b) return 0;
- } else if (sortOrder === 'desc') {
- if (_a < _b) return 1;
- if (_a > _b) return -1;
- if (_a === _b) return 0;
- }
- return 0;
- });
+ const cellValue = row0[i].content;
+ if (!column.align && cellValue && isNumeric(cellValue)) {
+ column.align = 'right';
+ }
- if (this.hasColumnById('_rowIndex')) {
- // update row index
- const srNoColIndex = this.getColumnIndexById('_rowIndex');
- this.rows = this.rows.map((row, index) => {
- return row.map(cell => {
- if (cell.colIndex === srNoColIndex) {
- cell.content = (index + 1) + '';
- }
- return cell;
+ return column;
});
- });
- }
- }
-
- reverseArray(array) {
- let left = null;
- let right = null;
- let length = array.length;
-
- for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
- const temporary = array[left];
-
- array[left] = array[right];
- array[right] = temporary;
- }
- }
-
- switchColumn(index1, index2) {
- // update columns
- const temp = this.columns[index1];
- this.columns[index1] = this.columns[index2];
- this.columns[index2] = temp;
-
- this.columns[index1].colIndex = index1;
- this.columns[index2].colIndex = index2;
-
- // update rows
- this.rows = this.rows.map(row => {
- const newCell1 = Object.assign({}, row[index1], { colIndex: index2 });
- const newCell2 = Object.assign({}, row[index2], { colIndex: index1 });
-
- let newRow = row.map(cell => {
- // make object copy
- return Object.assign({}, cell);
- });
-
- newRow[index2] = newCell1;
- newRow[index1] = newCell2;
-
- return newRow;
- });
- }
-
- removeColumn(index) {
- index = +index;
- const filter = cell => cell.colIndex !== index;
- const map = (cell, i) => Object.assign({}, cell, { colIndex: i });
- // update columns
- this.columns = this.columns
- .filter(filter)
- .map(map);
-
- // update rows
- this.rows = this.rows.map(row => {
- const newRow = row
- .filter(filter)
- .map(map);
-
- return newRow;
- });
- }
-
- updateRow(row, rowIndex) {
- if (row.length < this.columns.length) {
- if (this.hasColumnById('_rowIndex')) {
- const val = (rowIndex + 1) + '';
-
- row = [val].concat(row);
- }
-
- if (this.hasColumnById('_checkbox')) {
- const val = '';
-
- row = [val].concat(row);
- }
}
- const _row = this.prepareRow(row, rowIndex);
- const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
- this.rows[index] = _row;
+ prepareRows() {
+ this.validateData(this.data);
- return _row;
- }
+ this.rows = this.data.map((d, i) => {
+ const index = this._getNextRowCount();
- updateCell(colIndex, rowIndex, options) {
- let cell;
- if (typeof colIndex === 'object') {
- // cell object was passed,
- // must have colIndex, rowIndex
- cell = colIndex;
- colIndex = cell.colIndex;
- rowIndex = cell.rowIndex;
- // the object passed must be merged with original cell
- options = cell;
- }
- cell = this.getCell(colIndex, rowIndex);
+ let row = [];
- // mutate object directly
- for (let key in options) {
- const newVal = options[key];
- if (newVal !== undefined) {
- cell[key] = newVal;
- }
+ if (Array.isArray(d)) {
+ // row is an array
+ if (this.options.addCheckboxColumn) {
+ row.push(this.getCheckboxHTML());
+ }
+ if (this.options.addSerialNoColumn) {
+ row.push((index + 1) + '');
+ }
+ row = row.concat(d);
+
+ while (row.length < this.columns.length) {
+ row.push('');
+ }
+
+ } else {
+ // row is an object
+ for (let col of this.columns) {
+ if (col.id === '_checkbox') {
+ row.push(this.getCheckboxHTML());
+ } else if (col.id === '_rowIndex') {
+ row.push((index + 1) + '');
+ } else {
+ row.push(d[col.id]);
+ }
+ }
+ }
+
+ return this.prepareRow(row, {
+ rowIndex: index
+ });
+ });
}
- return cell;
- }
+ prepareRow(row, props) {
+ const baseRowCell = {
+ rowIndex: props.rowIndex
+ };
- updateColumn(colIndex, keyValPairs) {
- const column = this.getColumn(colIndex);
- for (let key in keyValPairs) {
- const newVal = keyValPairs[key];
- if (newVal !== undefined) {
- column[key] = newVal;
- }
- }
- return column;
- }
+ row = row
+ .map((cell, i) => this.prepareCell(cell, i))
+ .map(cell => Object.assign({}, baseRowCell, cell));
- filterRows(keyword, colIndex) {
- let rowsToHide = [];
- let rowsToShow = [];
- const cells = this.rows.map(row => row[colIndex]);
-
- cells.forEach(cell => {
- const hay = cell.content.toLowerCase();
- const needle = (keyword || '').toLowerCase();
-
- if (!needle || hay.includes(needle)) {
- rowsToShow.push(cell.rowIndex);
- } else {
- rowsToHide.push(cell.rowIndex);
- }
- });
-
- return {rowsToHide, rowsToShow};
- }
-
- getRowCount() {
- return this.rowCount;
- }
-
- _getNextRowCount() {
- const val = this.rowCount;
-
- this.rowCount++;
- return val;
- }
-
- getRows(start, end) {
- return this.rows.slice(start, end);
- }
-
- getColumns(skipStandardColumns) {
- let columns = this.columns;
-
- if (skipStandardColumns) {
- columns = columns.slice(this.getStandardColumnCount());
+ // monkey patched in array object
+ row.meta = props;
+ return row;
}
- return columns;
- }
+ validateColumns() {
+ const columns = this.options.columns;
+ if (!Array.isArray(columns)) {
+ throw new DataError('`columns` must be an array');
+ }
- getStandardColumnCount() {
- if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
- return 2;
+ columns.forEach((column, i) => {
+ if (typeof column !== 'string' && typeof column !== 'object') {
+ throw new DataError(`column "${i}" must be a string or an object`);
+ }
+ });
}
- if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
- return 1;
+ validateData(data) {
+ if (Array.isArray(data) &&
+ (data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
+ return true;
+ }
+ throw new DataError('`data` must be an array of arrays or objects');
}
- return 0;
- }
+ appendRows(rows) {
+ this.validateData(rows);
- getColumnCount(skipStandardColumns) {
- let val = this.columns.length;
-
- if (skipStandardColumns) {
- val = val - this.getStandardColumnCount();
+ this.rows = this.rows.concat(this.prepareRows(rows));
}
- return val;
- }
+ sortRows(colIndex, sortOrder = 'none') {
+ colIndex = +colIndex;
- getColumn(colIndex) {
- colIndex = +colIndex;
- return this.columns.find(col => col.colIndex === colIndex);
- }
+ // reset sortOrder and update for colIndex
+ this.getColumns()
+ .map(col => {
+ if (col.colIndex === colIndex) {
+ col.sortOrder = sortOrder;
+ } else {
+ col.sortOrder = 'none';
+ }
+ });
- getRow(rowIndex) {
- rowIndex = +rowIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex);
- }
+ this._sortRows(colIndex, sortOrder);
+ }
- getCell(colIndex, rowIndex) {
- rowIndex = +rowIndex;
- colIndex = +colIndex;
- return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
- }
+ _sortRows(colIndex, sortOrder) {
- get() {
- return {
- columns: this.columns,
- rows: this.rows
- };
- }
+ if (this.currentSort.colIndex === colIndex) {
+ // reverse the array if only sortOrder changed
+ if (
+ (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
+ (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
+ ) {
+ this.reverseArray(this.rows);
+ this.currentSort.sortOrder = sortOrder;
+ return;
+ }
+ }
- hasColumn(name) {
- return Boolean(this.columns.find(col => col.content === name));
- }
+ 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;
- hasColumnById(id) {
- return Boolean(this.columns.find(col => col.id === id));
- }
+ if (sortOrder === 'none') {
+ return _aIndex - _bIndex;
+ } else if (sortOrder === 'asc') {
+ if (_a < _b) return -1;
+ if (_a > _b) return 1;
+ if (_a === _b) return 0;
+ } else if (sortOrder === 'desc') {
+ if (_a < _b) return 1;
+ if (_a > _b) return -1;
+ if (_a === _b) return 0;
+ }
+ return 0;
+ });
- getColumnIndex(name) {
- return this.columns.findIndex(col => col.content === name);
- }
+ if (this.hasColumnById('_rowIndex')) {
+ // update row index
+ const srNoColIndex = this.getColumnIndexById('_rowIndex');
+ this.rows = this.rows.map((row, index) => {
+ return row.map(cell => {
+ if (cell.colIndex === srNoColIndex) {
+ cell.content = (index + 1) + '';
+ }
+ return cell;
+ });
+ });
+ }
+ }
- getColumnIndexById(id) {
- return this.columns.findIndex(col => col.id === id);
- }
+ reverseArray(array) {
+ let left = null;
+ let right = null;
+ let length = array.length;
- getCheckboxHTML() {
- return '';
- }
+ for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
+ const temporary = array[left];
+
+ array[left] = array[right];
+ array[right] = temporary;
+ }
+ }
+
+ switchColumn(index1, index2) {
+ // update columns
+ const temp = this.columns[index1];
+ this.columns[index1] = this.columns[index2];
+ this.columns[index2] = temp;
+
+ this.columns[index1].colIndex = index1;
+ this.columns[index2].colIndex = index2;
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newCell1 = Object.assign({}, row[index1], {
+ colIndex: index2
+ });
+ const newCell2 = Object.assign({}, row[index2], {
+ colIndex: index1
+ });
+
+ let newRow = row.map(cell => {
+ // make object copy
+ return Object.assign({}, cell);
+ });
+
+ newRow[index2] = newCell1;
+ newRow[index1] = newCell2;
+
+ return newRow;
+ });
+ }
+
+ removeColumn(index) {
+ index = +index;
+ const filter = cell => cell.colIndex !== index;
+ const map = (cell, i) => Object.assign({}, cell, {
+ colIndex: i
+ });
+ // update columns
+ this.columns = this.columns
+ .filter(filter)
+ .map(map);
+
+ // update rows
+ this.rows = this.rows.map(row => {
+ const newRow = row
+ .filter(filter)
+ .map(map);
+
+ return newRow;
+ });
+ }
+
+ updateRow(row, rowIndex) {
+ if (row.length < this.columns.length) {
+ if (this.hasColumnById('_rowIndex')) {
+ const val = (rowIndex + 1) + '';
+
+ row = [val].concat(row);
+ }
+
+ if (this.hasColumnById('_checkbox')) {
+ const val = '';
+
+ row = [val].concat(row);
+ }
+ }
+
+ const _row = this.prepareRow(row, rowIndex);
+ const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
+ this.rows[index] = _row;
+
+ return _row;
+ }
+
+ updateCell(colIndex, rowIndex, options) {
+ let cell;
+ if (typeof colIndex === 'object') {
+ // cell object was passed,
+ // must have colIndex, rowIndex
+ cell = colIndex;
+ colIndex = cell.colIndex;
+ rowIndex = cell.rowIndex;
+ // the object passed must be merged with original cell
+ options = cell;
+ }
+ cell = this.getCell(colIndex, rowIndex);
+
+ // mutate object directly
+ for (let key in options) {
+ const newVal = options[key];
+ if (newVal !== undefined) {
+ cell[key] = newVal;
+ }
+ }
+
+ return cell;
+ }
+
+ updateColumn(colIndex, keyValPairs) {
+ const column = this.getColumn(colIndex);
+ for (let key in keyValPairs) {
+ const newVal = keyValPairs[key];
+ if (newVal !== undefined) {
+ column[key] = newVal;
+ }
+ }
+ return column;
+ }
+
+ filterRows(keyword, colIndex) {
+ let rowsToHide = [];
+ let rowsToShow = [];
+ const cells = this.rows.map(row => row[colIndex]);
+
+ cells.forEach(cell => {
+ const hay = cell.content.toLowerCase();
+ const needle = (keyword || '').toLowerCase();
+
+ if (!needle || hay.includes(needle)) {
+ rowsToShow.push(cell.rowIndex);
+ } else {
+ rowsToHide.push(cell.rowIndex);
+ }
+ });
+
+ return {
+ rowsToHide,
+ rowsToShow
+ };
+ }
+
+ getRowCount() {
+ return this.rowCount;
+ }
+
+ _getNextRowCount() {
+ const val = this.rowCount;
+
+ this.rowCount++;
+ return val;
+ }
+
+ getRows(start, end) {
+ return this.rows.slice(start, end);
+ }
+
+ getColumns(skipStandardColumns) {
+ let columns = this.columns;
+
+ if (skipStandardColumns) {
+ columns = columns.slice(this.getStandardColumnCount());
+ }
+
+ return columns;
+ }
+
+ getStandardColumnCount() {
+ if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
+ return 2;
+ }
+
+ if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ getColumnCount(skipStandardColumns) {
+ let val = this.columns.length;
+
+ if (skipStandardColumns) {
+ val = val - this.getStandardColumnCount();
+ }
+
+ return val;
+ }
+
+ getColumn(colIndex) {
+ colIndex = +colIndex;
+ return this.columns.find(col => col.colIndex === colIndex);
+ }
+
+ getRow(rowIndex) {
+ rowIndex = +rowIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex);
+ }
+
+ getCell(colIndex, rowIndex) {
+ rowIndex = +rowIndex;
+ colIndex = +colIndex;
+ return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
+ }
+
+ get() {
+ return {
+ columns: this.columns,
+ rows: this.rows
+ };
+ }
+
+ hasColumn(name) {
+ return Boolean(this.columns.find(col => col.content === name));
+ }
+
+ hasColumnById(id) {
+ return Boolean(this.columns.find(col => col.id === id));
+ }
+
+ getColumnIndex(name) {
+ return this.columns.findIndex(col => col.content === name);
+ }
+
+ getColumnIndexById(id) {
+ return this.columns.findIndex(col => col.id === id);
+ }
+
+ getCheckboxHTML() {
+ return '';
+ }
}
// Custom Errors
diff --git a/src/datatable.js b/src/datatable.js
index 89d6aec..33bad20 100644
--- a/src/datatable.js
+++ b/src/datatable.js
@@ -10,50 +10,48 @@ import DEFAULT_OPTIONS from './defaults';
import './style.css';
class DataTable {
- constructor(wrapper, options) {
- DataTable.instances++;
+ constructor(wrapper, options) {
+ DataTable.instances++;
- if (typeof wrapper === 'string') {
- // css selector
- wrapper = document.querySelector(wrapper);
- }
- this.wrapper = wrapper;
- if (!(this.wrapper instanceof HTMLElement)) {
- throw new Error('Invalid argument given for `wrapper`');
+ if (typeof wrapper === 'string') {
+ // css selector
+ wrapper = document.querySelector(wrapper);
+ }
+ this.wrapper = wrapper;
+ if (!(this.wrapper instanceof HTMLElement)) {
+ throw new Error('Invalid argument given for `wrapper`');
+ }
+
+ this.options = Object.assign({}, DEFAULT_OPTIONS, options);
+ this.options.headerDropdown =
+ DEFAULT_OPTIONS.headerDropdown
+ .concat(options.headerDropdown || []);
+ // custom user events
+ this.events = Object.assign({}, DEFAULT_OPTIONS.events, options.events || {});
+ this.fireEvent = this.fireEvent.bind(this);
+
+ this.prepare();
+
+ this.style = new Style(this);
+ this.keyboard = new Keyboard(this.wrapper);
+ this.datamanager = new DataManager(this.options);
+ this.rowmanager = new RowManager(this);
+ this.columnmanager = new ColumnManager(this);
+ this.cellmanager = new CellManager(this);
+ this.bodyRenderer = new BodyRenderer(this);
+
+ if (this.options.data) {
+ this.refresh();
+ }
}
- this.options = Object.assign({}, DEFAULT_OPTIONS, options);
- this.options.headerDropdown =
- DEFAULT_OPTIONS.headerDropdown
- .concat(options.headerDropdown || []);
- // custom user events
- this.events = Object.assign(
- {}, DEFAULT_OPTIONS.events, options.events || {}
- );
- this.fireEvent = this.fireEvent.bind(this);
-
- this.prepare();
-
- this.style = new Style(this);
- this.keyboard = new Keyboard(this.wrapper);
- this.datamanager = new DataManager(this.options);
- this.rowmanager = new RowManager(this);
- this.columnmanager = new ColumnManager(this);
- this.cellmanager = new CellManager(this);
- this.bodyRenderer = new BodyRenderer(this);
-
- if (this.options.data) {
- this.refresh();
+ prepare() {
+ this.prepareDom();
+ this.unfreeze();
}
- }
- prepare() {
- this.prepareDom();
- this.unfreeze();
- }
-
- prepareDom() {
- this.wrapper.innerHTML = `
+ prepareDom() {
+ this.wrapper.innerHTML = `
@@ -67,110 +65,110 @@ class DataTable {
`;
- this.datatableWrapper = $('.data-table', this.wrapper);
- this.header = $('.data-table-header', this.wrapper);
- this.bodyScrollable = $('.body-scrollable', this.wrapper);
- this.freezeContainer = $('.freeze-container', this.wrapper);
- }
-
- refresh(data) {
- this.datamanager.init(data);
- this.render();
- this.setDimensions();
- }
-
- destroy() {
- this.wrapper.innerHTML = '';
- this.style.destroy();
- }
-
- appendRows(rows) {
- this.datamanager.appendRows(rows);
- this.rowmanager.refreshRows();
- }
-
- refreshRow(row, rowIndex) {
- this.rowmanager.refreshRow(row, rowIndex);
- }
-
- render() {
- this.renderHeader();
- this.renderBody();
- }
-
- renderHeader() {
- this.columnmanager.renderHeader();
- }
-
- renderBody() {
- this.bodyRenderer.render();
- }
-
- setDimensions() {
- this.style.setDimensions();
- }
-
- getColumn(colIndex) {
- return this.datamanager.getColumn(colIndex);
- }
-
- getColumns() {
- return this.datamanager.getColumns();
- }
-
- getRows() {
- return this.datamanager.getRows();
- }
-
- getCell(colIndex, rowIndex) {
- return this.datamanager.getCell(colIndex, rowIndex);
- }
-
- getColumnHeaderElement(colIndex) {
- return this.columnmanager.getColumnHeaderElement(colIndex);
- }
-
- getViewportHeight() {
- if (!this.viewportHeight) {
- this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ this.datatableWrapper = $('.data-table', this.wrapper);
+ this.header = $('.data-table-header', this.wrapper);
+ this.bodyScrollable = $('.body-scrollable', this.wrapper);
+ this.freezeContainer = $('.freeze-container', this.wrapper);
}
- return this.viewportHeight;
- }
-
- sortColumn(colIndex, sortOrder) {
- this.columnmanager.sortColumn(colIndex, sortOrder);
- }
-
- removeColumn(colIndex) {
- this.columnmanager.removeColumn(colIndex);
- }
-
- scrollToLastColumn() {
- this.datatableWrapper.scrollLeft = 9999;
- }
-
- freeze() {
- $.style(this.freezeContainer, {
- display: ''
- });
- }
-
- unfreeze() {
- $.style(this.freezeContainer, {
- display: 'none'
- });
- }
-
- fireEvent(eventName, ...args) {
- this.events[eventName].apply(this, args);
- }
-
- log() {
- if (this.options.enableLogs) {
- console.log.apply(console, arguments);
+ refresh(data) {
+ this.datamanager.init(data);
+ this.render();
+ this.setDimensions();
+ }
+
+ destroy() {
+ this.wrapper.innerHTML = '';
+ this.style.destroy();
+ }
+
+ appendRows(rows) {
+ this.datamanager.appendRows(rows);
+ this.rowmanager.refreshRows();
+ }
+
+ refreshRow(row, rowIndex) {
+ this.rowmanager.refreshRow(row, rowIndex);
+ }
+
+ render() {
+ this.renderHeader();
+ this.renderBody();
+ }
+
+ renderHeader() {
+ this.columnmanager.renderHeader();
+ }
+
+ renderBody() {
+ this.bodyRenderer.render();
+ }
+
+ setDimensions() {
+ this.style.setDimensions();
+ }
+
+ getColumn(colIndex) {
+ return this.datamanager.getColumn(colIndex);
+ }
+
+ getColumns() {
+ return this.datamanager.getColumns();
+ }
+
+ getRows() {
+ return this.datamanager.getRows();
+ }
+
+ getCell(colIndex, rowIndex) {
+ return this.datamanager.getCell(colIndex, rowIndex);
+ }
+
+ getColumnHeaderElement(colIndex) {
+ return this.columnmanager.getColumnHeaderElement(colIndex);
+ }
+
+ getViewportHeight() {
+ if (!this.viewportHeight) {
+ this.viewportHeight = $.style(this.bodyScrollable, 'height');
+ }
+
+ return this.viewportHeight;
+ }
+
+ sortColumn(colIndex, sortOrder) {
+ this.columnmanager.sortColumn(colIndex, sortOrder);
+ }
+
+ removeColumn(colIndex) {
+ this.columnmanager.removeColumn(colIndex);
+ }
+
+ scrollToLastColumn() {
+ this.datatableWrapper.scrollLeft = 9999;
+ }
+
+ freeze() {
+ $.style(this.freezeContainer, {
+ display: ''
+ });
+ }
+
+ unfreeze() {
+ $.style(this.freezeContainer, {
+ display: 'none'
+ });
+ }
+
+ fireEvent(eventName, ...args) {
+ this.events[eventName].apply(this, args);
+ }
+
+ log() {
+ if (this.options.enableLogs) {
+ console.log.apply(console, arguments);
+ }
}
- }
}
DataTable.instances = 0;
diff --git a/src/defaults.js b/src/defaults.js
index f306723..30f228e 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -1,51 +1,51 @@
export default {
- columns: [],
- data: [],
- dropdownButton: '▼',
- headerDropdown: [
- {
- label: 'Sort Ascending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'asc');
- }
+ columns: [],
+ data: [],
+ dropdownButton: '▼',
+ headerDropdown: [
+ {
+ label: 'Sort Ascending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'asc');
+ }
+ },
+ {
+ label: 'Sort Descending',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'desc');
+ }
+ },
+ {
+ label: 'Reset sorting',
+ action: function (column) {
+ this.sortColumn(column.colIndex, 'none');
+ }
+ },
+ {
+ label: 'Remove column',
+ action: function (column) {
+ this.removeColumn(column.colIndex);
+ }
+ }
+ ],
+ events: {
+ onRemoveColumn(column) {},
+ onSwitchColumn(column1, column2) {},
+ onSortColumn(column) {}
},
- {
- label: 'Sort Descending',
- action: function (column) {
- this.sortColumn(column.colIndex, 'desc');
- }
+ sortIndicator: {
+ asc: '↑',
+ desc: '↓',
+ none: ''
},
- {
- label: 'Reset sorting',
- action: function (column) {
- this.sortColumn(column.colIndex, 'none');
- }
- },
- {
- label: 'Remove column',
- action: function (column) {
- this.removeColumn(column.colIndex);
- }
- }
- ],
- events: {
- onRemoveColumn(column) {},
- onSwitchColumn(column1, column2) {},
- onSortColumn(column) {}
- },
- sortIndicator: {
- asc: '↑',
- desc: '↓',
- none: ''
- },
- freezeMessage: '',
- getEditor: () => {},
- addSerialNoColumn: true,
- addCheckboxColumn: false,
- enableClusterize: true,
- enableLogs: false,
- layout: 'fixed', // fixed, fluid
- noDataMessage: 'No Data',
- cellHeight: null,
- enableInlineFilters: false
+ freezeMessage: '',
+ getEditor: () => {},
+ addSerialNoColumn: true,
+ addCheckboxColumn: false,
+ enableClusterize: true,
+ enableLogs: false,
+ layout: 'fixed', // fixed, fluid
+ noDataMessage: 'No Data',
+ cellHeight: null,
+ enableInlineFilters: false
};
diff --git a/src/dom.js b/src/dom.js
index 29b2cf5..f930a90 100644
--- a/src/dom.js
+++ b/src/dom.js
@@ -1,172 +1,181 @@
-
export default function $(expr, con) {
- return typeof expr === 'string' ?
- (con || document).querySelector(expr) :
- expr || null;
+ return typeof expr === 'string' ?
+ (con || document).querySelector(expr) :
+ expr || null;
}
$.each = (expr, con) => {
- return typeof expr === 'string' ?
- Array.from((con || document).querySelectorAll(expr)) :
- expr || null;
+ return typeof expr === 'string' ?
+ Array.from((con || document).querySelectorAll(expr)) :
+ expr || null;
};
$.create = (tag, o) => {
- let element = document.createElement(tag);
+ let element = document.createElement(tag);
- for (let i in o) {
- let val = o[i];
+ for (let i in o) {
+ let val = o[i];
- if (i === 'inside') {
- $(val).appendChild(element);
- } else
- if (i === 'around') {
- let ref = $(val);
- ref.parentNode.insertBefore(element, ref);
- element.appendChild(ref);
- } else
- if (i === 'styles') {
- if (typeof val === 'object') {
- Object.keys(val).map(prop => {
- element.style[prop] = val[prop];
- });
- }
+ if (i === 'inside') {
+ $(val).appendChild(element);
} else
- if (i in element) {
+ if (i === 'around') {
+ let ref = $(val);
+ ref.parentNode.insertBefore(element, ref);
+ element.appendChild(ref);
+ } else
+ if (i === 'styles') {
+ if (typeof val === 'object') {
+ Object.keys(val).map(prop => {
+ element.style[prop] = val[prop];
+ });
+ }
+ } else
+ if (i in element) {
element[i] = val;
- } else {
+ } else {
element.setAttribute(i, val);
- }
- }
+ }
+ }
- return element;
+ return element;
};
$.on = (element, event, selector, callback) => {
- if (!callback) {
- callback = selector;
- $.bind(element, event, callback);
- } else {
- $.delegate(element, event, selector, callback);
- }
+ if (!callback) {
+ callback = selector;
+ $.bind(element, event, callback);
+ } else {
+ $.delegate(element, event, selector, callback);
+ }
};
$.off = (element, event, handler) => {
- element.removeEventListener(event, handler);
+ element.removeEventListener(event, handler);
};
$.bind = (element, event, callback) => {
- event.split(/\s+/).forEach(function (event) {
- element.addEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.addEventListener(event, callback);
+ });
};
$.delegate = (element, event, selector, callback) => {
- element.addEventListener(event, function (e) {
- const delegatedTarget = e.target.closest(selector);
- if (delegatedTarget) {
- e.delegatedTarget = delegatedTarget;
- callback.call(this, e, delegatedTarget);
- }
- });
+ element.addEventListener(event, function (e) {
+ const delegatedTarget = e.target.closest(selector);
+ if (delegatedTarget) {
+ e.delegatedTarget = delegatedTarget;
+ callback.call(this, e, delegatedTarget);
+ }
+ });
};
$.unbind = (element, o) => {
- if (element) {
- for (let event in o) {
- let callback = o[event];
+ if (element) {
+ for (let event in o) {
+ let callback = o[event];
- event.split(/\s+/).forEach(function (event) {
- element.removeEventListener(event, callback);
- });
+ event.split(/\s+/).forEach(function (event) {
+ element.removeEventListener(event, callback);
+ });
+ }
}
- }
};
$.fire = (target, type, properties) => {
- let evt = document.createEvent('HTMLEvents');
+ let evt = document.createEvent('HTMLEvents');
- evt.initEvent(type, true, true);
+ evt.initEvent(type, true, true);
- for (let j in properties) {
- evt[j] = properties[j];
- }
+ for (let j in properties) {
+ evt[j] = properties[j];
+ }
- return target.dispatchEvent(evt);
+ return target.dispatchEvent(evt);
};
$.data = (element, attrs) => { // eslint-disable-line
- if (!attrs) {
- return element.dataset;
- }
+ if (!attrs) {
+ return element.dataset;
+ }
- for (const attr in attrs) {
- element.dataset[attr] = attrs[attr];
- }
+ for (const attr in attrs) {
+ element.dataset[attr] = attrs[attr];
+ }
};
$.style = (elements, styleMap) => { // eslint-disable-line
- if (typeof styleMap === 'string') {
- return $.getStyle(elements, styleMap);
- }
-
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- elements.map(element => {
- for (const prop in styleMap) {
- element.style[prop] = styleMap[prop];
+ if (typeof styleMap === 'string') {
+ return $.getStyle(elements, styleMap);
}
- });
+
+ if (!Array.isArray(elements)) {
+ elements = [elements];
+ }
+
+ elements.map(element => {
+ for (const prop in styleMap) {
+ element.style[prop] = styleMap[prop];
+ }
+ });
};
$.removeStyle = (elements, styleProps) => {
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
-
- if (!Array.isArray(styleProps)) {
- styleProps = [styleProps];
- }
-
- elements.map(element => {
- for (const prop of styleProps) {
- element.style[prop] = '';
+ if (!Array.isArray(elements)) {
+ elements = [elements];
}
- });
+
+ if (!Array.isArray(styleProps)) {
+ styleProps = [styleProps];
+ }
+
+ elements.map(element => {
+ for (const prop of styleProps) {
+ element.style[prop] = '';
+ }
+ });
};
$.getStyle = (element, prop) => {
- let val = getComputedStyle(element)[prop];
+ let val = getComputedStyle(element)[prop];
- if (['width', 'height'].includes(prop)) {
- val = parseFloat(val);
- }
+ if (['width', 'height'].includes(prop)) {
+ val = parseFloat(val);
+ }
- return val;
+ return val;
};
$.closest = (selector, element) => {
- if (!element) return null;
+ if (!element) return null;
- if (element.matches(selector)) {
- return element;
- }
+ if (element.matches(selector)) {
+ return element;
+ }
- return $.closest(selector, element.parentNode);
+ return $.closest(selector, element.parentNode);
};
$.inViewport = (el, parentEl) => {
- const { top, left, bottom, right } = el.getBoundingClientRect();
- const { top: pTop, left: pLeft, bottom: pBottom, right: pRight } = parentEl.getBoundingClientRect();
+ const {
+ top,
+ left,
+ bottom,
+ right
+ } = el.getBoundingClientRect();
+ const {
+ top: pTop,
+ left: pLeft,
+ bottom: pBottom,
+ right: pRight
+ } = parentEl.getBoundingClientRect();
- return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
+ return top >= pTop && left >= pLeft && bottom <= pBottom && right <= pRight;
};
$.scrollTop = function scrollTop(element, pixels) {
- requestAnimationFrame(() => {
- element.scrollTop = pixels;
- });
+ requestAnimationFrame(() => {
+ element.scrollTop = pixels;
+ });
};
diff --git a/src/keyboard.js b/src/keyboard.js
index abd9e96..77f2cbe 100644
--- a/src/keyboard.js
+++ b/src/keyboard.js
@@ -1,56 +1,56 @@
import $ from './dom';
const KEYCODES = {
- 13: 'enter',
- 91: 'meta',
- 16: 'shift',
- 17: 'ctrl',
- 18: 'alt',
- 37: 'left',
- 38: 'up',
- 39: 'right',
- 40: 'down',
- 9: 'tab',
- 27: 'esc',
- 67: 'c',
- 70: 'f'
+ 13: 'enter',
+ 91: 'meta',
+ 16: 'shift',
+ 17: 'ctrl',
+ 18: 'alt',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 9: 'tab',
+ 27: 'esc',
+ 67: 'c',
+ 70: 'f'
};
export default class Keyboard {
- constructor(element) {
- this.listeners = {};
- $.on(element, 'keydown', this.handler.bind(this));
- }
-
- handler(e) {
- let key = KEYCODES[e.keyCode];
-
- if (e.shiftKey && key !== 'shift') {
- key = 'shift+' + key;
+ constructor(element) {
+ this.listeners = {};
+ $.on(element, 'keydown', this.handler.bind(this));
}
- if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
- key = 'ctrl+' + key;
- }
+ handler(e) {
+ let key = KEYCODES[e.keyCode];
- const listeners = this.listeners[key];
-
- if (listeners && listeners.length > 0) {
- for (let listener of listeners) {
- const preventBubbling = listener(e);
- if (preventBubbling === undefined || preventBubbling === true) {
- e.preventDefault();
+ if (e.shiftKey && key !== 'shift') {
+ key = 'shift+' + key;
+ }
+
+ if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
+ key = 'ctrl+' + key;
+ }
+
+ const listeners = this.listeners[key];
+
+ if (listeners && listeners.length > 0) {
+ for (let listener of listeners) {
+ const preventBubbling = listener(e);
+ if (preventBubbling === undefined || preventBubbling === true) {
+ e.preventDefault();
+ }
+ }
}
- }
}
- }
- on(key, listener) {
- const keys = key.split(',').map(k => k.trim());
+ on(key, listener) {
+ const keys = key.split(',').map(k => k.trim());
- keys.map(key => {
- this.listeners[key] = this.listeners[key] || [];
- this.listeners[key].push(listener);
- });
- }
+ keys.map(key => {
+ this.listeners[key] = this.listeners[key] || [];
+ this.listeners[key].push(listener);
+ });
+ }
}
diff --git a/src/performance.js b/src/performance.js
deleted file mode 100644
index 9dcd0de..0000000
--- a/src/performance.js
+++ /dev/null
@@ -1,14 +0,0 @@
-class Performance {
- start() {
- this._start = window.performance.now();
- }
-
- end() {
- this._end = window.performance.now();
- console.log(this._end - this._start);
- }
-}
-
-let perf = new Performance();
-
-export default perf;
diff --git a/src/rowmanager.js b/src/rowmanager.js
index f601045..28fb68d 100644
--- a/src/rowmanager.js
+++ b/src/rowmanager.js
@@ -1,210 +1,224 @@
import $ from './dom';
-import { makeDataAttributeString, promisify } from './utils';
+import {
+ makeDataAttributeString,
+ promisify
+} from './utils';
export default class RowManager {
- constructor(instance) {
- this.instance = instance;
- this.options = this.instance.options;
- this.wrapper = this.instance.wrapper;
- this.bodyScrollable = this.instance.bodyScrollable;
+ constructor(instance) {
+ this.instance = instance;
+ this.options = this.instance.options;
+ this.wrapper = this.instance.wrapper;
+ this.bodyScrollable = this.instance.bodyScrollable;
- this.bindEvents();
- this.refreshRows = promisify(this.refreshRows, this);
- }
-
- get datamanager() {
- return this.instance.datamanager;
- }
-
- get cellmanager() {
- return this.instance.cellmanager;
- }
-
- bindEvents() {
- this.bindCheckbox();
- }
-
- bindCheckbox() {
- if (!this.options.addCheckboxColumn) return;
-
- // map of checked rows
- this.checkMap = [];
-
- $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
- const $cell = $checkbox.closest('.data-table-col');
- const { rowIndex, isHeader } = $.data($cell);
- const checked = $checkbox.checked;
-
- if (isHeader) {
- this.checkAll(checked);
- } else {
- this.checkRow(rowIndex, checked);
- }
- });
- }
-
- refreshRows() {
- this.instance.renderBody();
- this.instance.setDimensions();
- }
-
- refreshRow(row, rowIndex) {
- const _row = this.datamanager.updateRow(row, rowIndex);
-
- _row.forEach(cell => {
- this.cellmanager.refreshCell(cell);
- });
- }
-
- getCheckedRows() {
- if (!this.checkMap) {
- return [];
+ this.bindEvents();
+ this.refreshRows = promisify(this.refreshRows, this);
}
- return this.checkMap
- .map((c, rowIndex) => {
- if (c) {
- return rowIndex;
+ get datamanager() {
+ return this.instance.datamanager;
+ }
+
+ get cellmanager() {
+ return this.instance.cellmanager;
+ }
+
+ bindEvents() {
+ this.bindCheckbox();
+ }
+
+ bindCheckbox() {
+ if (!this.options.addCheckboxColumn) return;
+
+ // map of checked rows
+ this.checkMap = [];
+
+ $.on(this.wrapper, 'click', '.data-table-col[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => {
+ const $cell = $checkbox.closest('.data-table-col');
+ const {
+ rowIndex,
+ isHeader
+ } = $.data($cell);
+ const checked = $checkbox.checked;
+
+ if (isHeader) {
+ this.checkAll(checked);
+ } else {
+ this.checkRow(rowIndex, checked);
+ }
+ });
+ }
+
+ refreshRows() {
+ this.instance.renderBody();
+ this.instance.setDimensions();
+ }
+
+ refreshRow(row, rowIndex) {
+ const _row = this.datamanager.updateRow(row, rowIndex);
+
+ _row.forEach(cell => {
+ this.cellmanager.refreshCell(cell);
+ });
+ }
+
+ getCheckedRows() {
+ if (!this.checkMap) {
+ return [];
}
- return null;
- })
- .filter(c => {
- return c !== null || c !== undefined;
- });
- }
- highlightCheckedRows() {
- this.getCheckedRows()
- .map(rowIndex => this.checkRow(rowIndex, true));
- }
-
- checkRow(rowIndex, toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- this.checkMap[rowIndex] = value;
- // set checkbox value explicitly
- $.each(`.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`, this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight row
- this.highlightRow(rowIndex, toggle);
- }
-
- checkAll(toggle) {
- const value = toggle ? 1 : 0;
-
- // update internal map
- if (toggle) {
- this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
- } else {
- this.checkMap = [];
- }
- // set checkbox value
- $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
- .map(input => {
- input.checked = toggle;
- });
- // highlight all
- this.highlightAll(toggle);
- }
-
- highlightRow(rowIndex, toggle = true) {
- const $row = this.getRow$(rowIndex);
- if (!$row) return;
-
- if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
- $row.classList.add('row-unhighlight');
- return;
+ return this.checkMap
+ .map((c, rowIndex) => {
+ if (c) {
+ return rowIndex;
+ }
+ return null;
+ })
+ .filter(c => {
+ return c !== null || c !== undefined;
+ });
}
- if (toggle && $row.classList.contains('row-unhighlight')) {
- $row.classList.remove('row-unhighlight');
+ highlightCheckedRows() {
+ this.getCheckedRows()
+ .map(rowIndex => this.checkRow(rowIndex, true));
}
- this._highlightedRows = this._highlightedRows || {};
-
- if (toggle) {
- $row.classList.add('row-highlight');
- this._highlightedRows[rowIndex] = $row;
- } else {
- $row.classList.remove('row-highlight');
- delete this._highlightedRows[rowIndex];
- }
- }
-
- highlightAll(toggle = true) {
- if (toggle) {
- this.bodyScrollable.classList.add('row-highlight-all');
- } else {
- this.bodyScrollable.classList.remove('row-highlight-all');
- for (const rowIndex in this._highlightedRows) {
- const $row = this._highlightedRows[rowIndex];
- $row.classList.remove('row-highlight');
- }
- this._highlightedRows = {};
- }
- }
-
- getRow$(rowIndex) {
- return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
- }
-
- getTotalRows() {
- return this.datamanager.getRowCount();
- }
-
- getFirstRowIndex() {
- return 0;
- }
-
- getLastRowIndex() {
- return this.datamanager.getRowCount() - 1;
- }
-
- scrollToRow(rowIndex) {
- rowIndex = +rowIndex;
- this._lastScrollTo = this._lastScrollTo || 0;
- const $row = this.getRow$(rowIndex);
- if ($.inViewport($row, this.bodyScrollable)) return;
-
- const { height } = $row.getBoundingClientRect();
- const { top, bottom } = this.bodyScrollable.getBoundingClientRect();
- const rowsInView = Math.floor((bottom - top) / height);
-
- let offset = 0;
- if (rowIndex > this._lastScrollTo) {
- offset = height * ((rowIndex + 1) - rowsInView);
- } else {
- offset = height * ((rowIndex + 1) - 1);
+ checkRow(rowIndex, toggle) {
+ const value = toggle ? 1 : 0;
+ const selector = rowIndex =>
+ `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`;
+ // update internal map
+ this.checkMap[rowIndex] = value;
+ // set checkbox value explicitly
+ $.each(selector(rowIndex), this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight row
+ this.highlightRow(rowIndex, toggle);
}
- this._lastScrollTo = rowIndex;
- $.scrollTop(this.bodyScrollable, offset);
- }
+ checkAll(toggle) {
+ const value = toggle ? 1 : 0;
- getRowHTML(row, props) {
- const dataAttr = makeDataAttributeString(props);
-
- if (props.isFilter) {
- row = row.map(cell => (Object.assign(cell, {
- content: this.getFilterInput({ colIndex: cell.colIndex }),
- isFilter: 1,
- isHeader: undefined,
- editable: false
- })));
+ // update internal map
+ if (toggle) {
+ this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
+ } else {
+ this.checkMap = [];
+ }
+ // set checkbox value
+ $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
+ .map(input => {
+ input.checked = toggle;
+ });
+ // highlight all
+ this.highlightAll(toggle);
}
- return `
-
- ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
-
- `;
- }
+ highlightRow(rowIndex, toggle = true) {
+ const $row = this.getRow$(rowIndex);
+ if (!$row) return;
- getFilterInput(props) {
- const dataAttr = makeDataAttributeString(props);
- return ``;
- }
+ if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
+ $row.classList.add('row-unhighlight');
+ return;
+ }
+
+ if (toggle && $row.classList.contains('row-unhighlight')) {
+ $row.classList.remove('row-unhighlight');
+ }
+
+ this._highlightedRows = this._highlightedRows || {};
+
+ if (toggle) {
+ $row.classList.add('row-highlight');
+ this._highlightedRows[rowIndex] = $row;
+ } else {
+ $row.classList.remove('row-highlight');
+ delete this._highlightedRows[rowIndex];
+ }
+ }
+
+ highlightAll(toggle = true) {
+ if (toggle) {
+ this.bodyScrollable.classList.add('row-highlight-all');
+ } else {
+ this.bodyScrollable.classList.remove('row-highlight-all');
+ for (const rowIndex in this._highlightedRows) {
+ const $row = this._highlightedRows[rowIndex];
+ $row.classList.remove('row-highlight');
+ }
+ this._highlightedRows = {};
+ }
+ }
+
+ getRow$(rowIndex) {
+ return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
+ }
+
+ getTotalRows() {
+ return this.datamanager.getRowCount();
+ }
+
+ getFirstRowIndex() {
+ return 0;
+ }
+
+ getLastRowIndex() {
+ return this.datamanager.getRowCount() - 1;
+ }
+
+ scrollToRow(rowIndex) {
+ rowIndex = +rowIndex;
+ this._lastScrollTo = this._lastScrollTo || 0;
+ const $row = this.getRow$(rowIndex);
+ if ($.inViewport($row, this.bodyScrollable)) return;
+
+ const {
+ height
+ } = $row.getBoundingClientRect();
+ const {
+ top,
+ bottom
+ } = this.bodyScrollable.getBoundingClientRect();
+ const rowsInView = Math.floor((bottom - top) / height);
+
+ let offset = 0;
+ if (rowIndex > this._lastScrollTo) {
+ offset = height * ((rowIndex + 1) - rowsInView);
+ } else {
+ offset = height * ((rowIndex + 1) - 1);
+ }
+
+ this._lastScrollTo = rowIndex;
+ $.scrollTop(this.bodyScrollable, offset);
+ }
+
+ getRowHTML(row, props) {
+ const dataAttr = makeDataAttributeString(props);
+
+ if (props.isFilter) {
+ row = row.map(cell => (Object.assign(cell, {
+ content: this.getFilterInput({
+ colIndex: cell.colIndex
+ }),
+ isFilter: 1,
+ isHeader: undefined,
+ editable: false
+ })));
+ }
+
+ return `
+
+ ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
+
+ `;
+ }
+
+ getFilterInput(props) {
+ const dataAttr = makeDataAttributeString(props);
+ return ``;
+ }
}
diff --git a/src/style.css b/src/style.css
index 9ed0c29..9cd2d1a 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1,3 +1,4 @@
+/* This file is processed by postcss */
/* variables */
:root {
diff --git a/src/style.js b/src/style.js
index c55d569..0e29d32 100644
--- a/src/style.js
+++ b/src/style.js
@@ -39,7 +39,7 @@ export default class Style {
this.styleEl.remove();
}
- setStyle(rule, styleMap, index = -1) {
+ setStyle(selector, styleMap, index = -1) {
const styles = Object.keys(styleMap)
.map(prop => {
if (!prop.includes('-')) {
@@ -48,7 +48,12 @@ export default class Style {
return `${prop}:${styleMap[prop]};`;
})
.join('');
- let ruleString = `.${this.scopeClass} ${rule} { ${styles} }`;
+ let prefixedSelector = selector
+ .split(',')
+ .map(r => `.${this.scopeClass} ${r}`)
+ .join(',');
+
+ let ruleString = `${prefixedSelector} { ${styles} }`;
let _index = this.styleSheet.cssRules.length;
if (index !== -1) {
diff --git a/src/utils.js b/src/utils.js
index e2d2be5..e2969a1 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -2,153 +2,153 @@ import _throttle from 'lodash/throttle';
import _debounce from 'lodash/debounce';
export function camelCaseToDash(str) {
- return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
+ return str.replace(/([A-Z])/g, (g) => `-${g[0].toLowerCase()}`);
}
export function makeDataAttributeString(props) {
- const keys = Object.keys(props);
+ const keys = Object.keys(props);
- return keys
- .map((key) => {
- const _key = camelCaseToDash(key);
- const val = props[key];
+ return keys
+ .map((key) => {
+ const _key = camelCaseToDash(key);
+ const val = props[key];
- if (val === undefined) return '';
- return `data-${_key}="${val}" `;
- })
- .join('')
- .trim();
+ if (val === undefined) return '';
+ return `data-${_key}="${val}" `;
+ })
+ .join('')
+ .trim();
}
export function getDefault(a, b) {
- return a !== undefined ? a : b;
+ return a !== undefined ? a : b;
}
export function escapeRegExp(str) {
- // https://stackoverflow.com/a/6969486
- return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
+ // https://stackoverflow.com/a/6969486
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
export function getCSSString(styleMap) {
- let style = '';
+ let style = '';
- for (const prop in styleMap) {
- if (styleMap.hasOwnProperty(prop)) {
- style += `${prop}: ${styleMap[prop]}; `;
+ for (const prop in styleMap) {
+ if (styleMap.hasOwnProperty(prop)) {
+ style += `${prop}: ${styleMap[prop]}; `;
+ }
}
- }
- return style.trim();
+ return style.trim();
}
export function getCSSRuleBlock(rule, styleMap) {
- return `${rule} { ${getCSSString(styleMap)} }`;
+ return `${rule} { ${getCSSString(styleMap)} }`;
}
export function buildCSSRule(rule, styleMap, cssRulesString = '') {
- // build css rules efficiently,
- // append new rule if doesnt exist,
- // update existing ones
+ // build css rules efficiently,
+ // append new rule if doesnt exist,
+ // update existing ones
- const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
- const rulePattern = new RegExp(rulePatternStr, 'g');
+ const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
+ const rulePattern = new RegExp(rulePatternStr, 'g');
- if (cssRulesString && cssRulesString.match(rulePattern)) {
- for (const property in styleMap) {
- const value = styleMap[property];
- const propPattern = new RegExp(`${escapeRegExp(property)}:([^;]*);`);
+ if (cssRulesString && cssRulesString.match(rulePattern)) {
+ for (const property in styleMap) {
+ const value = styleMap[property];
+ const propPattern = new RegExp(`${escapeRegExp(property)}:([^;]*);`);
- cssRulesString = cssRulesString.replace(rulePattern, function (match, propertyStr) {
- if (propertyStr.match(propPattern)) {
- // property exists, replace value with new value
- propertyStr = propertyStr.replace(propPattern, (match, valueStr) => {
- return `${property}: ${value};`;
- });
+ cssRulesString = cssRulesString.replace(rulePattern, function (match, propertyStr) {
+ if (propertyStr.match(propPattern)) {
+ // property exists, replace value with new value
+ propertyStr = propertyStr.replace(propPattern, (match, valueStr) => {
+ return `${property}: ${value};`;
+ });
+ }
+ propertyStr = propertyStr.trim();
+
+ const replacer =
+ `${rule} { ${propertyStr} }`;
+
+ return replacer;
+ });
}
- propertyStr = propertyStr.trim();
- const replacer =
- `${rule} { ${propertyStr} }`;
-
- return replacer;
- });
+ return cssRulesString;
}
-
- return cssRulesString;
- }
- // no match, append new rule block
- return `${cssRulesString}${getCSSRuleBlock(rule, styleMap)}`;
+ // no match, append new rule block
+ return `${cssRulesString}${getCSSRuleBlock(rule, styleMap)}`;
}
export function removeCSSRule(rule, cssRulesString = '') {
- const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
- const rulePattern = new RegExp(rulePatternStr, 'g');
- let output = cssRulesString;
+ const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
+ const rulePattern = new RegExp(rulePatternStr, 'g');
+ let output = cssRulesString;
- if (cssRulesString && cssRulesString.match(rulePattern)) {
- output = cssRulesString.replace(rulePattern, '');
- }
+ if (cssRulesString && cssRulesString.match(rulePattern)) {
+ output = cssRulesString.replace(rulePattern, '');
+ }
- return output.trim();
+ return output.trim();
}
export function copyTextToClipboard(text) {
- // https://stackoverflow.com/a/30810322/5353542
- var textArea = document.createElement('textarea');
+ // https://stackoverflow.com/a/30810322/5353542
+ var textArea = document.createElement('textarea');
- //
- // *** This styling is an extra step which is likely not required. ***
- //
- // Why is it here? To ensure:
- // 1. the element is able to have focus and selection.
- // 2. if element was to flash render it has minimal visual impact.
- // 3. less flakyness with selection and copying which **might** occur if
- // the textarea element is not visible.
- //
- // The likelihood is the element won't even render, not even a flash,
- // so some of these are just precautions. However in IE the element
- // is visible whilst the popup box asking the user for permission for
- // the web page to copy to the clipboard.
- //
+ //
+ // *** This styling is an extra step which is likely not required. ***
+ //
+ // Why is it here? To ensure:
+ // 1. the element is able to have focus and selection.
+ // 2. if element was to flash render it has minimal visual impact.
+ // 3. less flakyness with selection and copying which **might** occur if
+ // the textarea element is not visible.
+ //
+ // The likelihood is the element won't even render, not even a flash,
+ // so some of these are just precautions. However in IE the element
+ // is visible whilst the popup box asking the user for permission for
+ // the web page to copy to the clipboard.
+ //
- // Place in top-left corner of screen regardless of scroll position.
- textArea.style.position = 'fixed';
- textArea.style.top = 0;
- textArea.style.left = 0;
+ // Place in top-left corner of screen regardless of scroll position.
+ textArea.style.position = 'fixed';
+ textArea.style.top = 0;
+ textArea.style.left = 0;
- // Ensure it has a small width and height. Setting to 1px / 1em
- // doesn't work as this gives a negative w/h on some browsers.
- textArea.style.width = '2em';
- textArea.style.height = '2em';
+ // Ensure it has a small width and height. Setting to 1px / 1em
+ // doesn't work as this gives a negative w/h on some browsers.
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
- // We don't need padding, reducing the size if it does flash render.
- textArea.style.padding = 0;
+ // We don't need padding, reducing the size if it does flash render.
+ textArea.style.padding = 0;
- // Clean up any borders.
- textArea.style.border = 'none';
- textArea.style.outline = 'none';
- textArea.style.boxShadow = 'none';
+ // Clean up any borders.
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
- // Avoid flash of white box if rendered for any reason.
- textArea.style.background = 'transparent';
+ // Avoid flash of white box if rendered for any reason.
+ textArea.style.background = 'transparent';
- textArea.value = text;
+ textArea.value = text;
- document.body.appendChild(textArea);
+ document.body.appendChild(textArea);
- textArea.select();
+ textArea.select();
- try {
- document.execCommand('copy');
- } catch (err) {
- console.log('Oops, unable to copy');
- }
+ try {
+ document.execCommand('copy');
+ } catch (err) {
+ console.log('Oops, unable to copy');
+ }
- document.body.removeChild(textArea);
+ document.body.removeChild(textArea);
}
export function isNumeric(val) {
- return !isNaN(val);
+ return !isNaN(val);
}
export let throttle = _throttle;
@@ -156,30 +156,30 @@ export let throttle = _throttle;
export let debounce = _debounce;
export function promisify(fn, context = null) {
- return (...args) => {
- return new Promise(resolve => {
- setTimeout(() => {
- const out = fn.apply(context, args);
- resolve(out);
- }, 0);
- });
- };
+ return (...args) => {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ const out = fn.apply(context, args);
+ resolve(out);
+ }, 0);
+ });
+ };
};
export function chainPromises(promises) {
- return promises.reduce(
- (prev, cur) => prev.then(cur), Promise.resolve()
- );
+ return promises.reduce(
+ (prev, cur) => prev.then(cur), Promise.resolve()
+ );
};
export function linkProperties(target, source, properties) {
- const props = properties.reduce((acc, prop) => {
- acc[prop] = {
- get() {
- return source[prop];
- }
- };
- return acc;
- }, {});
- Object.defineProperties(target, props);
+ const props = properties.reduce((acc, prop) => {
+ acc[prop] = {
+ get() {
+ return source[prop];
+ }
+ };
+ return acc;
+ }, {});
+ Object.defineProperties(target, props);
};