commit
86fd87fa38
@ -22,7 +22,7 @@
|
||||
"it": true,
|
||||
"expect": true,
|
||||
"sinon": true,
|
||||
"$": true
|
||||
"Clusterize": true
|
||||
},
|
||||
|
||||
"parser": "babel-eslint",
|
||||
@ -62,7 +62,7 @@
|
||||
"max-params": 0,
|
||||
"max-statements": 0,
|
||||
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
||||
"newline-after-var": [2, "always"],
|
||||
"newline-after-var": [0, "always"],
|
||||
"new-parens": 2,
|
||||
"no-alert": 0,
|
||||
"no-array-constructor": 2,
|
||||
|
||||
@ -21,11 +21,11 @@
|
||||
<section style="width: 60%; font-size: 12px;">
|
||||
|
||||
</section>
|
||||
<script src="./node_modules/jquery/dist/jquery.js"></script>
|
||||
|
||||
<script src="./node_modules/clusterize.js/clusterize.js"></script>
|
||||
<script src="./lib/frappe-datatable.js"></script>
|
||||
<script>
|
||||
$(() => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
var data = {
|
||||
"columns": [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,45 +1,37 @@
|
||||
import { getCellContent, copyTextToClipboard } from './utils';
|
||||
import keyboard from 'keyboard';
|
||||
import $ from './dom';
|
||||
|
||||
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.prepare();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
prepare() {
|
||||
this.$borderOutline = this.instance.$borders.find('.border-outline');
|
||||
this.$borderBg = this.instance.$borders.find('.border-background');
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.bindFocusCell();
|
||||
this.bindEditCell();
|
||||
this.bindKeyboardNav();
|
||||
this.bindKeyboardSelection();
|
||||
this.bindCopyCellContents();
|
||||
this.bindMouseEvents();
|
||||
}
|
||||
|
||||
bindFocusCell() {
|
||||
const bodyScrollable = this.instance.bodyScrollable;
|
||||
|
||||
this.$focusedCell = null;
|
||||
bodyScrollable.on('click', '.data-table-col', (e) => {
|
||||
this.focusCell($(e.currentTarget));
|
||||
});
|
||||
this.bindKeyboardNav();
|
||||
}
|
||||
|
||||
bindEditCell() {
|
||||
const self = this;
|
||||
|
||||
this.$editingCell = null;
|
||||
this.bodyScrollable.on('dblclick', '.data-table-col', function () {
|
||||
self.activateEditing($(this));
|
||||
|
||||
$.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => {
|
||||
this.activateEditing(cell);
|
||||
});
|
||||
|
||||
keyboard.on('enter', (e) => {
|
||||
@ -53,8 +45,8 @@ export default class CellManager {
|
||||
}
|
||||
});
|
||||
|
||||
$(document.body).on('click', e => {
|
||||
if ($(e.target).is('.edit-cell, .edit-cell *')) return;
|
||||
$.on(document.body, 'click', e => {
|
||||
if (e.target.matches('.edit-cell, .edit-cell *')) return;
|
||||
this.deactivateEditing();
|
||||
});
|
||||
}
|
||||
@ -87,7 +79,7 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
let $cell = this.$focusedCell;
|
||||
const { rowIndex, colIndex } = this.getCellAttr($cell);
|
||||
const { rowIndex, colIndex } = $.data($cell);
|
||||
|
||||
if (direction === 'left') {
|
||||
$cell = this.getLeftMostCell$(rowIndex);
|
||||
@ -107,7 +99,7 @@ export default class CellManager {
|
||||
if (!this.$focusedCell) return false;
|
||||
|
||||
if (!this.inViewport(this.$focusedCell)) {
|
||||
const { rowIndex } = this.getCellAttr(this.$focusedCell);
|
||||
const { rowIndex } = $.data(this.$focusedCell);
|
||||
|
||||
this.scrollToRow(rowIndex - this.getRowCountPerPage() + 2);
|
||||
return true;
|
||||
@ -165,27 +157,27 @@ export default class CellManager {
|
||||
bindMouseEvents() {
|
||||
let mouseDown = null;
|
||||
|
||||
this.bodyScrollable.on('mousedown', '.data-table-col', (e) => {
|
||||
$.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => {
|
||||
mouseDown = true;
|
||||
this.focusCell($(e.currentTarget));
|
||||
this.focusCell($(e.delegatedTarget));
|
||||
});
|
||||
|
||||
this.bodyScrollable.on('mouseup', () => {
|
||||
$.on(this.bodyScrollable, 'mouseup', () => {
|
||||
mouseDown = false;
|
||||
});
|
||||
|
||||
this.bodyScrollable.on('mousemove', '.data-table-col', (e) => {
|
||||
$.on(this.bodyScrollable, 'mousemove', '.data-table-col', (e) => {
|
||||
if (!mouseDown) return;
|
||||
this.selectArea($(e.currentTarget));
|
||||
this.selectArea($(e.delegatedTarget));
|
||||
});
|
||||
}
|
||||
|
||||
focusCell($cell) {
|
||||
if (!$cell.length) return;
|
||||
if (!$cell) return;
|
||||
|
||||
const { colIndex } = this.getCellAttr($cell);
|
||||
const { colIndex, isHeader } = $.data($cell);
|
||||
|
||||
if (this.isStandardCell(colIndex)) {
|
||||
if (this.isStandardCell(colIndex) || isHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -197,38 +189,33 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
if (this.$focusedCell) {
|
||||
this.$focusedCell.removeClass('selected');
|
||||
this.$focusedCell.classList.remove('selected');
|
||||
}
|
||||
|
||||
this.$focusedCell = $cell;
|
||||
$cell.addClass('selected');
|
||||
$cell.classList.add('selected');
|
||||
|
||||
this.highlightRowColumnHeader($cell);
|
||||
}
|
||||
|
||||
highlightRowColumnHeader($cell) {
|
||||
const { colIndex, rowIndex } = this.getCellAttr($cell);
|
||||
const _colIndex = this.instance.getSerialColumnIndex();
|
||||
const { colIndex, rowIndex } = $.data($cell);
|
||||
const _colIndex = this.columnmanager.getSerialColumnIndex();
|
||||
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.lastSelectors) {
|
||||
this.instance.removeStyle(this.lastSelectors.colHeaderSelector);
|
||||
this.instance.removeStyle(this.lastSelectors.rowHeaderSelector);
|
||||
if (this.lastHeaders) {
|
||||
$.removeStyle(this.lastHeaders, 'backgroundColor');
|
||||
}
|
||||
|
||||
this.instance.setStyle(colHeaderSelector, {
|
||||
'background-color': 'var(--light-bg)'
|
||||
const colHeader = $(colHeaderSelector, this.wrapper);
|
||||
const rowHeader = $(rowHeaderSelector, this.wrapper);
|
||||
|
||||
$.style([colHeader, rowHeader], {
|
||||
backgroundColor: 'var(--light-bg)'
|
||||
});
|
||||
|
||||
this.instance.setStyle(rowHeaderSelector, {
|
||||
'background-color': 'var(--light-bg)'
|
||||
});
|
||||
|
||||
this.lastSelectors = {
|
||||
colHeaderSelector,
|
||||
rowHeaderSelector
|
||||
};
|
||||
this.lastHeaders = [colHeader, rowHeader];
|
||||
}
|
||||
|
||||
selectArea($selectionCursor) {
|
||||
@ -241,13 +228,13 @@ export default class CellManager {
|
||||
};
|
||||
|
||||
_selectArea($cell1, $cell2) {
|
||||
const cells = this.getCellsInRange(...arguments);
|
||||
if ($cell1 === $cell2) return false;
|
||||
|
||||
const cells = this.getCellsInRange($cell1, $cell2);
|
||||
if (!cells) return false;
|
||||
this.clearSelection();
|
||||
const $cells = cells.map(([c, r]) => this.getCell$(r, c)[0]);
|
||||
|
||||
$($cells).addClass('highlight');
|
||||
this.clearSelection();
|
||||
cells.map(index => this.getCell$(...index)).map($cell => $cell.classList.add('highlight'));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -259,12 +246,12 @@ export default class CellManager {
|
||||
} else
|
||||
if (typeof $cell1 === 'object') {
|
||||
|
||||
if (!($cell1.length && $cell2.length)) {
|
||||
if (!($cell1 && $cell2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cell1 = this.getCellAttr($cell1);
|
||||
const cell2 = this.getCellAttr($cell2);
|
||||
const cell1 = $.data($cell1);
|
||||
const cell2 = $.data($cell2);
|
||||
|
||||
colIndex1 = cell1.colIndex;
|
||||
rowIndex1 = cell1.rowIndex;
|
||||
@ -306,7 +293,9 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
this.bodyScrollable.find('.data-table-col.highlight').removeClass('highlight');
|
||||
$.each('.data-table-col.highlight', this.bodyScrollable)
|
||||
.map(cell => cell.classList.remove('highlight'));
|
||||
|
||||
this.$selectionCursor = null;
|
||||
}
|
||||
|
||||
@ -315,15 +304,15 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
activateEditing($cell) {
|
||||
const { rowIndex, colIndex } = this.getCellAttr($cell);
|
||||
const col = this.instance.getColumn(colIndex);
|
||||
const { rowIndex, colIndex } = $.data($cell);
|
||||
const col = this.instance.columnmanager.getColumn(colIndex);
|
||||
|
||||
if (col && col.editable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$editingCell) {
|
||||
const { _rowIndex, _colIndex } = this.getCellAttr(this.$editingCell);
|
||||
const { _rowIndex, _colIndex } = $.data(this.$editingCell);
|
||||
|
||||
if (rowIndex === _rowIndex && colIndex === _colIndex) {
|
||||
// editing the same cell
|
||||
@ -332,9 +321,11 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
this.$editingCell = $cell;
|
||||
$cell.addClass('editing');
|
||||
$cell.classList.add('editing');
|
||||
|
||||
const $editCell = $('.edit-cell', $cell);
|
||||
$editCell.innerHTML = '';
|
||||
|
||||
const $editCell = $cell.find('.edit-cell').empty();
|
||||
const cell = this.getCell(colIndex, rowIndex);
|
||||
const editing = this.getEditingObject(colIndex, rowIndex, cell.content, $editCell);
|
||||
|
||||
@ -347,7 +338,7 @@ export default class CellManager {
|
||||
|
||||
deactivateEditing() {
|
||||
if (!this.$editingCell) return;
|
||||
this.$editingCell.removeClass('editing');
|
||||
this.$editingCell.classList.remove('editing');
|
||||
this.$editingCell = null;
|
||||
}
|
||||
|
||||
@ -357,26 +348,27 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
// editing fallback
|
||||
const $input = $('<input type="text" />');
|
||||
|
||||
parent.append($input);
|
||||
const $input = $.create('input', {
|
||||
type: 'text',
|
||||
inside: parent
|
||||
});
|
||||
|
||||
return {
|
||||
initValue(value) {
|
||||
$input.focus();
|
||||
return $input.val(value);
|
||||
$input.value = value;
|
||||
},
|
||||
getValue() {
|
||||
return $input.val();
|
||||
return $input.value;
|
||||
},
|
||||
setValue(value) {
|
||||
return $input.val(value);
|
||||
$input.value = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
submitEditing($cell) {
|
||||
const { rowIndex, colIndex } = this.getCellAttr($cell);
|
||||
const { rowIndex, colIndex } = $.data($cell);
|
||||
|
||||
if ($cell) {
|
||||
const editing = this.currentCellEditing;
|
||||
@ -400,7 +392,7 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
copyCellContents($cell1, $cell2) {
|
||||
const cells = this.getCellsInRange(...arguments);
|
||||
const cells = this.getCellsInRange($cell1, $cell2);
|
||||
|
||||
if (!cells) return;
|
||||
|
||||
@ -433,56 +425,56 @@ export default class CellManager {
|
||||
|
||||
refreshCell(cell) {
|
||||
const selector = `.data-table-col[data-row-index="${cell.rowIndex}"][data-col-index="${cell.colIndex}"]`;
|
||||
const $cell = this.bodyScrollable.find(selector);
|
||||
const $cell = $(selector, this.bodyScrollable);
|
||||
|
||||
$cell.html(getCellContent(cell));
|
||||
$cell.innerHTML = getCellContent(cell);
|
||||
}
|
||||
|
||||
isStandardCell(colIndex) {
|
||||
// Standard cells are in Sr. No and Checkbox column
|
||||
return colIndex < this.instance.getFirstColumnIndex();
|
||||
return colIndex < this.columnmanager.getFirstColumnIndex();
|
||||
}
|
||||
|
||||
getCell$(rowIndex, colIndex) {
|
||||
return this.bodyScrollable.find(`.data-table-col[data-row-index="${rowIndex}"][data-col-index="${colIndex}"]`);
|
||||
getCell$(colIndex, rowIndex) {
|
||||
return $(`.data-table-col[data-row-index="${rowIndex}"][data-col-index="${colIndex}"]`, this.bodyScrollable);
|
||||
}
|
||||
|
||||
getAboveCell$($cell) {
|
||||
const { colIndex } = this.getCellAttr($cell);
|
||||
const $aboveRow = $cell.parent().prev();
|
||||
const { colIndex } = $.data($cell);
|
||||
const $aboveRow = $cell.parentElement.previousElementSibling;
|
||||
|
||||
return $aboveRow.find(`[data-col-index="${colIndex}"]`);
|
||||
return $(`[data-col-index="${colIndex}"]`, $aboveRow);
|
||||
}
|
||||
|
||||
getBelowCell$($cell) {
|
||||
const { colIndex } = this.getCellAttr($cell);
|
||||
const $belowRow = $cell.parent().next();
|
||||
const { colIndex } = $.data($cell);
|
||||
const $belowRow = $cell.parentElement.nextElementSibling;
|
||||
|
||||
return $belowRow.find(`[data-col-index="${colIndex}"]`);
|
||||
return $(`[data-col-index="${colIndex}"]`, $belowRow);
|
||||
}
|
||||
|
||||
getLeftCell$($cell) {
|
||||
return $cell.prev();
|
||||
return $cell.previousElementSibling;
|
||||
}
|
||||
|
||||
getRightCell$($cell) {
|
||||
return $cell.next();
|
||||
return $cell.nextElementSibling;
|
||||
}
|
||||
|
||||
getLeftMostCell$(rowIndex) {
|
||||
return this.getCell$(rowIndex, this.instance.getFirstColumnIndex());
|
||||
return this.getCell$(rowIndex, this.columnmanager.getFirstColumnIndex());
|
||||
}
|
||||
|
||||
getRightMostCell$(rowIndex) {
|
||||
return this.getCell$(rowIndex, this.instance.getLastColumnIndex());
|
||||
return this.getCell$(rowIndex, this.columnmanager.getLastColumnIndex());
|
||||
}
|
||||
|
||||
getTopMostCell$(colIndex) {
|
||||
return this.getCell$(0, colIndex);
|
||||
return this.getCell$(this.rowmanager.getFirstRowIndex(), colIndex);
|
||||
}
|
||||
|
||||
getBottomMostCell$(colIndex) {
|
||||
return this.getCell$(this.instance.getLastRowIndex(), colIndex);
|
||||
return this.getCell$(this.rowmanager.getLastRowIndex(), colIndex);
|
||||
}
|
||||
|
||||
getCell(colIndex, rowIndex) {
|
||||
@ -490,20 +482,20 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
getCellAttr($cell) {
|
||||
return $cell.data();
|
||||
return this.instance.getCellAttr($cell);
|
||||
}
|
||||
|
||||
getRowHeight() {
|
||||
return this.bodyScrollable.find('.data-table-row:first').height();
|
||||
return $.style($('.data-table-row', this.bodyScrollable), 'height');
|
||||
}
|
||||
|
||||
inViewport($cell) {
|
||||
let colIndex, rowIndex;
|
||||
let colIndex, rowIndex; // eslint-disable-line
|
||||
|
||||
if (typeof $cell === 'number') {
|
||||
[colIndex, rowIndex] = arguments;
|
||||
} else {
|
||||
let cell = this.getCellAttr($cell);
|
||||
let cell = $.data($cell);
|
||||
|
||||
colIndex = cell.colIndex;
|
||||
rowIndex = cell.rowIndex;
|
||||
@ -513,7 +505,7 @@ export default class CellManager {
|
||||
const rowHeight = this.getRowHeight();
|
||||
const rowOffset = rowIndex * rowHeight;
|
||||
|
||||
const scrollTopOffset = this.bodyScrollable[0].scrollTop;
|
||||
const scrollTopOffset = this.bodyScrollable.scrollTop;
|
||||
|
||||
if (rowOffset - scrollTopOffset + rowHeight < viewportHeight) {
|
||||
return true;
|
||||
@ -523,7 +515,7 @@ export default class CellManager {
|
||||
}
|
||||
|
||||
scrollToCell($cell) {
|
||||
const { rowIndex } = this.getCellAttr($cell);
|
||||
const { rowIndex } = $.data($cell);
|
||||
|
||||
this.scrollToRow(rowIndex);
|
||||
}
|
||||
@ -535,7 +527,7 @@ export default class CellManager {
|
||||
scrollToRow(rowIndex) {
|
||||
const offset = rowIndex * this.getRowHeight();
|
||||
|
||||
this.bodyScrollable[0].scrollTop = offset;
|
||||
this.bodyScrollable.scrollTop = offset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
271
src/columnmanager.js
Normal file
271
src/columnmanager.js
Normal file
@ -0,0 +1,271 @@
|
||||
import $ from './dom';
|
||||
import { getDefault, getHeaderHTML } from './utils';
|
||||
|
||||
export default class ColumnManager {
|
||||
constructor(instance) {
|
||||
this.instance = instance;
|
||||
this.options = this.instance.options;
|
||||
this.header = this.instance.header;
|
||||
this.datamanager = this.instance.datamanager;
|
||||
this.style = this.instance.style;
|
||||
this.wrapper = this.instance.wrapper;
|
||||
this.rowmanager = this.instance.rowmanager;
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
const columns = this.datamanager.getColumns();
|
||||
|
||||
this.header.innerHTML = getHeaderHTML(columns);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.bindResizeColumn();
|
||||
this.bindSortColumn();
|
||||
}
|
||||
|
||||
bindResizeColumn() {
|
||||
let isDragging = false;
|
||||
let $currCell, startWidth, startX;
|
||||
|
||||
$.on(this.header, 'mousedown', '.data-table-col', (e, $cell) => {
|
||||
$currCell = $cell;
|
||||
const { colIndex } = $.data($currCell);
|
||||
const col = this.getColumn(colIndex);
|
||||
|
||||
if (col && col.resizable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
isDragging = true;
|
||||
startWidth = $.style($('.content', $currCell), 'width');
|
||||
startX = e.pageX;
|
||||
});
|
||||
|
||||
$.on(document.body, 'mouseup', (e) => {
|
||||
if (!$currCell) return;
|
||||
isDragging = false;
|
||||
|
||||
const { colIndex } = $.data($currCell);
|
||||
const width = $.style($('.content', $currCell), 'width');
|
||||
|
||||
this.setColumnWidth(colIndex, width);
|
||||
this.instance.setBodyWidth();
|
||||
$currCell = null;
|
||||
});
|
||||
|
||||
$.on(document.body, 'mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
const finalWidth = startWidth + (e.pageX - startX);
|
||||
const { colIndex } = $.data($currCell);
|
||||
|
||||
if (this.getColumnMinWidth(colIndex) > finalWidth) {
|
||||
// don't resize past minWidth
|
||||
return;
|
||||
}
|
||||
|
||||
this.setColumnHeaderWidth(colIndex, finalWidth);
|
||||
});
|
||||
}
|
||||
|
||||
bindSortColumn() {
|
||||
|
||||
$.on(this.header, 'click', '.data-table-col .content span', (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'
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
if (this.events && this.events.onSort) {
|
||||
this.events.onSort(colIndex, nextSortOrder);
|
||||
} else {
|
||||
this.sortRows(colIndex, nextSortOrder);
|
||||
this.rowmanager.refreshRows();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDimensions() {
|
||||
this.setHeaderStyle();
|
||||
this.setupMinWidth();
|
||||
this.setNaturalColumnWidth();
|
||||
this.distributeRemainingWidth();
|
||||
this.setColumnAlignments();
|
||||
}
|
||||
|
||||
setHeaderStyle() {
|
||||
if (!this.options.takeAvailableSpace) {
|
||||
// setting width as 0 will ensure that the
|
||||
// header doesn't take the available space
|
||||
$.style(this.header, {
|
||||
width: 0
|
||||
});
|
||||
}
|
||||
|
||||
$.style(this.header, {
|
||||
margin: 0
|
||||
});
|
||||
}
|
||||
|
||||
setupMinWidth() {
|
||||
// cache minWidth for each column
|
||||
this.minWidthMap = getDefault(this.minWidthMap, []);
|
||||
|
||||
$.each('.data-table-col', this.header).map(col => {
|
||||
const width = $.style($('.content', col), 'width');
|
||||
const { colIndex } = $.data(col);
|
||||
|
||||
if (!this.minWidthMap[colIndex]) {
|
||||
// only set this once
|
||||
this.minWidthMap[colIndex] = width;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setNaturalColumnWidth() {
|
||||
// set initial width as naturally calculated by table's first row
|
||||
$.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
|
||||
|
||||
let width = $.style($('.content', $cell), 'width');
|
||||
const height = $.style($('.content', $cell), 'height');
|
||||
const { colIndex } = $.data($cell);
|
||||
const minWidth = this.getColumnMinWidth(colIndex);
|
||||
|
||||
if (width < minWidth) {
|
||||
width = minWidth;
|
||||
}
|
||||
this.setColumnWidth(colIndex, width);
|
||||
this.setDefaultCellHeight(height);
|
||||
});
|
||||
}
|
||||
|
||||
distributeRemainingWidth() {
|
||||
if (!this.options.takeAvailableSpace) return;
|
||||
|
||||
const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
|
||||
const headerWidth = $.style(this.header, 'width');
|
||||
|
||||
if (headerWidth >= wrapperWidth) {
|
||||
// don't resize, horizontal scroll takes place
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaWidth = (wrapperWidth - headerWidth) / this.datamanager.getColumnCount(true);
|
||||
|
||||
this.datamanager.getColumns(true).map(col => {
|
||||
const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
|
||||
let finalWidth = Math.min(width + deltaWidth) - 2;
|
||||
|
||||
this.setColumnHeaderWidth(col.colIndex, finalWidth);
|
||||
this.setColumnWidth(col.colIndex, finalWidth);
|
||||
});
|
||||
this.instance.setBodyWidth();
|
||||
}
|
||||
|
||||
setDefaultCellHeight(height) {
|
||||
this.style.setStyle('.data-table-col .content', {
|
||||
height: height + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
setColumnAlignments() {
|
||||
// align columns
|
||||
this.getColumns()
|
||||
.map(column => {
|
||||
if (column.align && ['left', 'center', 'right'].includes(column.align)) {
|
||||
this.style.setStyle(`.data-table [data-col-index="${column.colIndex}"]`, {
|
||||
'text-align': column.align
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sortRows(colIndex, sortOrder) {
|
||||
this.datamanager.sortRows(colIndex, sortOrder);
|
||||
}
|
||||
|
||||
getColumn(colIndex) {
|
||||
return this.datamanager.getColumn(colIndex);
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return this.datamanager.getColumns();
|
||||
}
|
||||
|
||||
setColumnWidth(colIndex, width) {
|
||||
const selector = `[data-col-index="${colIndex}"] .content, [data-col-index="${colIndex}"] .edit-cell`;
|
||||
|
||||
this.style.setStyle(selector, {
|
||||
width: width + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
setColumnHeaderWidth(colIndex, width) {
|
||||
this.style.setStyle(`[data-col-index="${colIndex}"][data-is-header] .content`, {
|
||||
width: width + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
getColumnMinWidth(colIndex) {
|
||||
colIndex = +colIndex;
|
||||
return this.minWidthMap && this.minWidthMap[colIndex];
|
||||
}
|
||||
|
||||
getFirstColumnIndex() {
|
||||
if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
getLastColumnIndex() {
|
||||
return this.datamanager.getColumnCount() - 1;
|
||||
}
|
||||
|
||||
getColumnHeaderElement(colIndex) {
|
||||
colIndex = +colIndex;
|
||||
if (colIndex < 0) return null;
|
||||
return $(`.data-table-col[data-is-header][data-col-index="${colIndex}"]`, this.wrapper);
|
||||
}
|
||||
|
||||
getSerialColumnIndex() {
|
||||
const columns = this.datamanager.getColumns();
|
||||
|
||||
return columns.findIndex(column => column.content.includes('Sr. No'));
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import { isNumeric } from './utils';
|
||||
|
||||
export default class DataManager {
|
||||
constructor(options) {
|
||||
@ -14,6 +15,8 @@ export default class DataManager {
|
||||
|
||||
this.columns = this.prepareColumns(columns);
|
||||
this.rows = this.prepareRows(rows);
|
||||
|
||||
this.prepareNumericColumns();
|
||||
}
|
||||
|
||||
prepareColumns(columns) {
|
||||
@ -25,7 +28,8 @@ export default class DataManager {
|
||||
const val = {
|
||||
content: 'Sr. No',
|
||||
editable: false,
|
||||
resizable: false
|
||||
resizable: false,
|
||||
align: 'center'
|
||||
};
|
||||
|
||||
columns = [val].concat(columns);
|
||||
@ -36,7 +40,8 @@ export default class DataManager {
|
||||
const val = {
|
||||
content: '<input type="checkbox" />',
|
||||
editable: false,
|
||||
resizable: false
|
||||
resizable: false,
|
||||
sortable: false
|
||||
};
|
||||
|
||||
columns = [val].concat(columns);
|
||||
@ -59,6 +64,19 @@ export default class DataManager {
|
||||
});
|
||||
}
|
||||
|
||||
prepareNumericColumns() {
|
||||
const row0 = this.getRow(0);
|
||||
this.columns = this.columns.map((column, i) => {
|
||||
|
||||
const cellValue = row0[i].content;
|
||||
if (!column.align && cellValue && isNumeric(cellValue)) {
|
||||
column.align = 'right';
|
||||
}
|
||||
|
||||
return column;
|
||||
});
|
||||
}
|
||||
|
||||
prepareRows(rows) {
|
||||
if (!Array.isArray(rows) || !Array.isArray(rows[0])) {
|
||||
throw new TypeError('`rows` must be an array of arrays');
|
||||
@ -163,12 +181,36 @@ export default class DataManager {
|
||||
return this.rows.slice(start, end);
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return this.columns;
|
||||
getColumns(skipStandardColumns) {
|
||||
let columns = this.columns;
|
||||
|
||||
if (skipStandardColumns) {
|
||||
columns = columns.slice(this.getStandardColumnCount());
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
getColumnCount() {
|
||||
return this.columns.length;
|
||||
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) {
|
||||
|
||||
461
src/datatable.js
461
src/datatable.js
@ -1,15 +1,15 @@
|
||||
/* globals $, Clusterize */
|
||||
import {
|
||||
getHeaderHTML,
|
||||
getBodyHTML,
|
||||
getRowHTML,
|
||||
buildCSSRule,
|
||||
removeCSSRule,
|
||||
getDefault
|
||||
getRowHTML
|
||||
} from './utils';
|
||||
|
||||
import $ from './dom';
|
||||
|
||||
import DataManager from './datamanager';
|
||||
import CellManager from './cellmanager';
|
||||
import ColumnManager from './columnmanager';
|
||||
import RowManager from './rowmanager';
|
||||
import Style from './style';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
@ -24,27 +24,27 @@ const DEFAULT_OPTIONS = {
|
||||
addCheckboxColumn: true,
|
||||
enableClusterize: true,
|
||||
enableLogs: false,
|
||||
takeAvailableSpace: false
|
||||
takeAvailableSpace: true
|
||||
};
|
||||
|
||||
export default class DataTable {
|
||||
constructor(wrapper, options) {
|
||||
|
||||
this.wrapper = $(wrapper);
|
||||
if (this.wrapper.length === 0) {
|
||||
this.wrapper = wrapper;
|
||||
if (!this.wrapper) {
|
||||
throw new Error('Invalid argument given for `wrapper`');
|
||||
}
|
||||
|
||||
this.options = Object.assign({}, DEFAULT_OPTIONS, options);
|
||||
// custom user events
|
||||
this.events = this.options.events;
|
||||
// map of checked rows
|
||||
this.checkMap = [];
|
||||
|
||||
// make dom, make style, bind events
|
||||
this.make();
|
||||
this.prepare();
|
||||
|
||||
this.style = new Style(this.wrapper);
|
||||
this.datamanager = new DataManager(this.options);
|
||||
this.rowmanager = new RowManager(this);
|
||||
this.columnmanager = new ColumnManager(this);
|
||||
this.cellmanager = new CellManager(this);
|
||||
|
||||
if (this.options.data) {
|
||||
@ -52,16 +52,12 @@ export default class DataTable {
|
||||
}
|
||||
}
|
||||
|
||||
make() {
|
||||
if (this.wrapper.find('.data-table').length === 0) {
|
||||
this.makeDom();
|
||||
this.makeStyle();
|
||||
this.bindEvents();
|
||||
}
|
||||
prepare() {
|
||||
this.prepareDom();
|
||||
}
|
||||
|
||||
makeDom() {
|
||||
this.wrapper.html(`
|
||||
prepareDom() {
|
||||
this.wrapper.innerHTML = `
|
||||
<div class="data-table">
|
||||
<table class="data-table-header">
|
||||
</table>
|
||||
@ -74,13 +70,11 @@ export default class DataTable {
|
||||
<div class="border-background"></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
`;
|
||||
|
||||
this.header = this.wrapper.find('.data-table-header');
|
||||
this.bodyScrollable = this.wrapper.find('.body-scrollable');
|
||||
// this.body = this.wrapper.find('.data-table-body');
|
||||
this.footer = this.wrapper.find('.data-table-footer');
|
||||
this.$borders = this.wrapper.find('.data-table-borders');
|
||||
this.datatableWrapper = $('.data-table', this.wrapper);
|
||||
this.header = $('.data-table-header', this.wrapper);
|
||||
this.bodyScrollable = $('.body-scrollable', this.wrapper);
|
||||
}
|
||||
|
||||
refresh(data) {
|
||||
@ -90,7 +84,7 @@ export default class DataTable {
|
||||
|
||||
appendRows(rows) {
|
||||
this.datamanager.appendRows(rows);
|
||||
this.render();
|
||||
this.rowmanager.refreshRows();
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -100,9 +94,7 @@ export default class DataTable {
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
const columns = this.datamanager.getColumns();
|
||||
|
||||
this.header.html(getHeaderHTML(columns));
|
||||
this.columnmanager.renderHeader();
|
||||
}
|
||||
|
||||
renderBody() {
|
||||
@ -116,22 +108,20 @@ export default class DataTable {
|
||||
renderBodyHTML() {
|
||||
const rows = this.datamanager.getRows();
|
||||
|
||||
this.bodyScrollable.html(`
|
||||
<table class="data-table-body table table-bordered">
|
||||
this.bodyScrollable.innerHTML = `
|
||||
<table class="data-table-body">
|
||||
${getBodyHTML(rows)}
|
||||
</table>
|
||||
`);
|
||||
`;
|
||||
}
|
||||
|
||||
renderBodyWithClusterize() {
|
||||
const self = this;
|
||||
|
||||
// empty body
|
||||
this.bodyScrollable.html(`
|
||||
<table class="data-table-body table table-bordered">
|
||||
this.bodyScrollable.innerHTML = `
|
||||
<table class="data-table-body">
|
||||
${getBodyHTML([])}
|
||||
</table>
|
||||
`);
|
||||
`;
|
||||
|
||||
this.start = 0;
|
||||
this.pageLength = 1000;
|
||||
@ -144,11 +134,11 @@ export default class DataTable {
|
||||
|
||||
this.clusterize = new Clusterize({
|
||||
rows: initialData,
|
||||
scrollElem: this.bodyScrollable.get(0),
|
||||
contentElem: this.bodyScrollable.find('tbody').get(0),
|
||||
scrollElem: this.bodyScrollable,
|
||||
contentElem: $('tbody', this.bodyScrollable),
|
||||
callbacks: {
|
||||
clusterChanged() {
|
||||
self.highlightCheckedRows();
|
||||
clusterChanged: () => {
|
||||
this.rowmanager.highlightCheckedRows();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -197,396 +187,33 @@ export default class DataTable {
|
||||
return rows.map((row) => getRowHTML(row, { rowIndex: row[0].rowIndex }));
|
||||
}
|
||||
|
||||
refreshRows() {
|
||||
this.renderBody();
|
||||
this.setDimensions();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.bindResizeColumn();
|
||||
this.bindSortColumn();
|
||||
this.bindCheckbox();
|
||||
}
|
||||
|
||||
setDimensions() {
|
||||
const self = this;
|
||||
this.columnmanager.setDimensions();
|
||||
|
||||
if (!this.options.takeAvailableSpace) {
|
||||
// setting width as 0 will ensure that the
|
||||
// header doesn't take the available space
|
||||
this.header.css({
|
||||
width: 0
|
||||
});
|
||||
}
|
||||
this.setBodyWidth();
|
||||
|
||||
this.header.css({
|
||||
$.style(this.bodyScrollable, {
|
||||
marginTop: $.style(this.header, 'height') + 'px'
|
||||
});
|
||||
|
||||
$.style($('table', this.bodyScrollable), {
|
||||
margin: 0
|
||||
});
|
||||
|
||||
// cache minWidth for each column
|
||||
this.minWidthMap = getDefault(this.minWidthMap, []);
|
||||
this.header.find('.data-table-col').each(function () {
|
||||
const col = $(this);
|
||||
const width = parseInt(col.find('.content').css('width'), 10);
|
||||
const colIndex = col.attr('data-col-index');
|
||||
|
||||
if (!self.minWidthMap[colIndex]) {
|
||||
// only set this once
|
||||
self.minWidthMap[colIndex] = width;
|
||||
}
|
||||
});
|
||||
|
||||
// set initial width as naturally calculated by table's first row
|
||||
this.bodyScrollable.find('.data-table-row[data-row-index="0"] .data-table-col').each(function () {
|
||||
const $cell = $(this);
|
||||
let width = parseInt($cell.find('.content').css('width'), 10);
|
||||
const height = parseInt($cell.find('.content').css('height'), 10);
|
||||
const { colIndex } = self.getCellAttr($cell);
|
||||
const minWidth = self.getColumnMinWidth(colIndex);
|
||||
|
||||
if (width < minWidth) {
|
||||
width = minWidth;
|
||||
}
|
||||
self.setColumnWidth(colIndex, width);
|
||||
self.setDefaultCellHeight(height);
|
||||
});
|
||||
|
||||
this.setBodyWidth();
|
||||
|
||||
this.setStyle('.data-table .body-scrollable', {
|
||||
'margin-top': this.header.height() + 'px'
|
||||
});
|
||||
|
||||
// center align Sr. No column
|
||||
if (this.options.addSerialNoColumn) {
|
||||
const index = this.getSerialColumnIndex();
|
||||
|
||||
this.setStyle(`.data-table [data-col-index="${index}"]`, {
|
||||
'text-align': 'center'
|
||||
});
|
||||
}
|
||||
|
||||
this.bodyScrollable.find('.table').css('margin', 0);
|
||||
}
|
||||
|
||||
bindResizeColumn() {
|
||||
const self = this;
|
||||
let isDragging = false;
|
||||
let $currCell, startWidth, startX;
|
||||
|
||||
this.header.on('mousedown', '.data-table-col', function (e) {
|
||||
$currCell = $(this);
|
||||
const colIndex = $currCell.attr('data-col-index');
|
||||
const col = self.getColumn(colIndex);
|
||||
|
||||
if (col && col.resizable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
isDragging = true;
|
||||
startWidth = $currCell.find('.content').width();
|
||||
startX = e.pageX;
|
||||
});
|
||||
|
||||
$('body').on('mouseup', function (e) {
|
||||
if (!$currCell) return;
|
||||
isDragging = false;
|
||||
const colIndex = $currCell.attr('data-col-index');
|
||||
|
||||
if ($currCell) {
|
||||
const width = parseInt($currCell.find('.content').css('width'), 10);
|
||||
|
||||
self.setColumnWidth(colIndex, width);
|
||||
self.setBodyWidth();
|
||||
$currCell = null;
|
||||
}
|
||||
});
|
||||
|
||||
$('body').on('mousemove', function (e) {
|
||||
if (!isDragging) return;
|
||||
const finalWidth = startWidth + (e.pageX - startX);
|
||||
const colIndex = $currCell.attr('data-col-index');
|
||||
|
||||
if (self.getColumnMinWidth(colIndex) > finalWidth) {
|
||||
// don't resize past minWidth
|
||||
return;
|
||||
}
|
||||
|
||||
self.setColumnHeaderWidth(colIndex, finalWidth);
|
||||
});
|
||||
}
|
||||
|
||||
bindSortColumn() {
|
||||
const self = this;
|
||||
|
||||
this.header.on('click', '.data-table-col .content span', function () {
|
||||
const $cell = $(this).closest('.data-table-col');
|
||||
let sortOrder = getDefault($cell.attr('data-sort-order'), 'none');
|
||||
const colIndex = $cell.attr('data-col-index');
|
||||
const col = self.getColumn(colIndex);
|
||||
|
||||
if (col && col.sortable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reset sort indicator
|
||||
self.header.find('.sort-indicator').text('');
|
||||
self.header.find('.data-table-col').attr('data-sort-order', 'none');
|
||||
|
||||
if (sortOrder === 'none') {
|
||||
$cell.attr('data-sort-order', 'asc');
|
||||
$cell.find('.sort-indicator').text('▲');
|
||||
} else if (sortOrder === 'asc') {
|
||||
$cell.attr('data-sort-order', 'desc');
|
||||
$cell.find('.sort-indicator').text('▼');
|
||||
} else if (sortOrder === 'desc') {
|
||||
$cell.attr('data-sort-order', 'none');
|
||||
$cell.find('.sort-indicator').text('');
|
||||
}
|
||||
|
||||
// sortWith this action
|
||||
sortOrder = $cell.attr('data-sort-order');
|
||||
|
||||
if (self.events && self.events.onSort) {
|
||||
self.events.onSort(colIndex, sortOrder);
|
||||
} else {
|
||||
self.sortRows(colIndex, sortOrder);
|
||||
self.refreshRows();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sortRows(colIndex, sortOrder) {
|
||||
this.datamanager.sortRows(colIndex, sortOrder);
|
||||
}
|
||||
|
||||
bindCheckbox() {
|
||||
if (!this.options.addCheckboxColumn) return;
|
||||
const self = this;
|
||||
|
||||
this.wrapper.on('click', '.data-table-col[data-col-index="0"] [type="checkbox"]', function () {
|
||||
const $checkbox = $(this);
|
||||
const $cell = $checkbox.closest('.data-table-col');
|
||||
const { rowIndex, isHeader } = self.getCellAttr($cell);
|
||||
const checked = $checkbox.is(':checked');
|
||||
|
||||
if (isHeader) {
|
||||
self.checkAll(checked);
|
||||
} else {
|
||||
self.checkRow(rowIndex, checked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCheckedRows() {
|
||||
|
||||
return this.checkMap
|
||||
.map((c, rowIndex) => {
|
||||
if (c) {
|
||||
return rowIndex;
|
||||
}
|
||||
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
|
||||
this.bodyScrollable
|
||||
.find(`.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`)
|
||||
.prop('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
|
||||
this.bodyScrollable
|
||||
.find('.data-table-col[data-col-index="0"] [type="checkbox"]')
|
||||
.prop('checked', toggle);
|
||||
// highlight all
|
||||
this.highlightAll(toggle);
|
||||
}
|
||||
|
||||
highlightRow(rowIndex, toggle = true) {
|
||||
const $row = this.bodyScrollable
|
||||
.find(`.data-table-row[data-row-index="${rowIndex}"]`);
|
||||
|
||||
if (toggle) {
|
||||
$row.addClass('row-highlight');
|
||||
} else {
|
||||
$row.removeClass('row-highlight');
|
||||
}
|
||||
}
|
||||
|
||||
highlightAll(toggle = true) {
|
||||
this.bodyScrollable
|
||||
.find('.data-table-row')
|
||||
.toggleClass('row-highlight', toggle);
|
||||
}
|
||||
|
||||
setColumnWidth(colIndex, width) {
|
||||
// set width for content
|
||||
this.setStyle(`[data-col-index="${colIndex}"] .content`, {
|
||||
width: width + 'px'
|
||||
});
|
||||
// set width for edit cell
|
||||
this.setStyle(`[data-col-index="${colIndex}"] .edit-cell`, {
|
||||
width: width + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
setColumnHeaderWidth(colIndex, width) {
|
||||
this.setStyle(`[data-col-index="${colIndex}"][data-is-header] .content`, {
|
||||
width: width + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultCellHeight(height) {
|
||||
this.setStyle('.data-table-col .content', {
|
||||
height: height + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
setRowHeight(rowIndex, height) {
|
||||
this.setStyle(`[data-row-index="${rowIndex}"] .content`, {
|
||||
height: height + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
setColumnWidths() {
|
||||
const availableWidth = this.wrapper.width();
|
||||
const headerWidth = this.header.width();
|
||||
|
||||
if (headerWidth > availableWidth) {
|
||||
// don't resize, horizontal scroll takes place
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = this.datamanager.getColumns();
|
||||
const deltaWidth = (availableWidth - headerWidth) / this.datamanager.getColumnCount();
|
||||
|
||||
columns.map(col => {
|
||||
const width = this.getColumnHeaderElement(col.colIndex).width();
|
||||
let finalWidth = width + deltaWidth - 16;
|
||||
|
||||
if (this.options.addSerialNoColumn && col.colIndex === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setColumnHeaderWidth(col.colIndex, finalWidth);
|
||||
this.setColumnWidth(col.colIndex, finalWidth);
|
||||
});
|
||||
this.setBodyWidth();
|
||||
}
|
||||
|
||||
setBodyWidth() {
|
||||
this.bodyScrollable.css(
|
||||
'width',
|
||||
parseInt(this.header.css('width'), 10)
|
||||
);
|
||||
}
|
||||
const width = $.style(this.header, 'width');
|
||||
|
||||
setStyle(rule, styleMap) {
|
||||
let styles = this.$style.text();
|
||||
|
||||
styles = buildCSSRule(rule, styleMap, styles);
|
||||
this.$style.html(styles);
|
||||
}
|
||||
|
||||
removeStyle(rule) {
|
||||
let styles = this.$style.text();
|
||||
|
||||
styles = removeCSSRule(rule, styles);
|
||||
this.$style.html(styles);
|
||||
}
|
||||
|
||||
makeStyle() {
|
||||
this.$style = $('<style data-id="datatable"></style>')
|
||||
.prependTo(this.wrapper);
|
||||
}
|
||||
|
||||
getColumn(colIndex) {
|
||||
return this.datamanager.getColumn(colIndex);
|
||||
}
|
||||
|
||||
getRow(rowIndex) {
|
||||
return this.datamanager.getRow(rowIndex);
|
||||
}
|
||||
|
||||
getCell(colIndex, rowIndex) {
|
||||
return this.datamanager.getCell(colIndex, rowIndex);
|
||||
$.style(this.bodyScrollable, { width: width + 'px' });
|
||||
}
|
||||
|
||||
getColumnHeaderElement(colIndex) {
|
||||
colIndex = +colIndex;
|
||||
if (colIndex < 0) return null;
|
||||
return this.wrapper.find(
|
||||
`.data-table-col[data-is-header][data-col-index="${colIndex}"]`
|
||||
);
|
||||
}
|
||||
|
||||
getColumnMinWidth(colIndex) {
|
||||
colIndex = +colIndex;
|
||||
return this.minWidthMap && this.minWidthMap[colIndex];
|
||||
}
|
||||
|
||||
getCellAttr($cell) {
|
||||
return $cell.data();
|
||||
}
|
||||
|
||||
getTotalRows() {
|
||||
return this.datamanager.getRowCount();
|
||||
}
|
||||
|
||||
getFirstColumnIndex() {
|
||||
if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
getLastColumnIndex() {
|
||||
return this.datamanager.getColumnCount() - 1;
|
||||
}
|
||||
|
||||
getLastRowIndex() {
|
||||
return this.datamanager.getRowCount() - 1;
|
||||
}
|
||||
|
||||
getSerialColumnIndex() {
|
||||
const columns = this.datamanager.getColumns();
|
||||
|
||||
return columns.findIndex(column => column.content.includes('Sr. No'));
|
||||
return this.columnmanager.getColumnHeaderElement(colIndex);
|
||||
}
|
||||
|
||||
getViewportHeight() {
|
||||
if (!this.viewportHeight) {
|
||||
this.viewportHeight = this.bodyScrollable.height();
|
||||
this.viewportHeight = $.style(this.bodyScrollable, 'height');
|
||||
}
|
||||
|
||||
return this.viewportHeight;
|
||||
|
||||
145
src/dom.js
Normal file
145
src/dom.js
Normal file
@ -0,0 +1,145 @@
|
||||
|
||||
export default function $(expr, con) {
|
||||
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;
|
||||
};
|
||||
|
||||
$.create = (tag, o) => {
|
||||
let element = document.createElement(tag);
|
||||
|
||||
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];
|
||||
});
|
||||
}
|
||||
} else
|
||||
if (i in element) {
|
||||
element[i] = val;
|
||||
} else {
|
||||
element.setAttribute(i, val);
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
$.on = (element, event, selector, callback) => {
|
||||
if (!callback) {
|
||||
callback = selector;
|
||||
$.bind(element, event, callback);
|
||||
} else {
|
||||
$.delegate(element, event, selector, callback);
|
||||
}
|
||||
};
|
||||
|
||||
$.bind = (element, 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.unbind = (element, o) => {
|
||||
if (element) {
|
||||
for (let event in o) {
|
||||
let callback = o[event];
|
||||
|
||||
event.split(/\s+/).forEach(function (event) {
|
||||
element.removeEventListener(event, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.fire = (target, type, properties) => {
|
||||
let evt = document.createEvent('HTMLEvents');
|
||||
|
||||
evt.initEvent(type, true, true);
|
||||
|
||||
for (let j in properties) {
|
||||
evt[j] = properties[j];
|
||||
}
|
||||
|
||||
return target.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
$.data = (element, attrs) => { // eslint-disable-line
|
||||
if (!attrs) {
|
||||
return element.dataset;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.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] = '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$.getStyle = (element, prop) => {
|
||||
let val = getComputedStyle(element)[prop];
|
||||
|
||||
if (['width', 'height'].includes(prop)) {
|
||||
val = parseFloat(val);
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
@ -1,3 +1,5 @@
|
||||
import $ from './dom';
|
||||
|
||||
const KEYCODES = {
|
||||
13: 'enter',
|
||||
91: 'meta',
|
||||
@ -16,7 +18,7 @@ const KEYCODES = {
|
||||
const handlers = {};
|
||||
|
||||
function bind() {
|
||||
$(document).on('keydown', handler);
|
||||
$.on(document, 'keydown', handler);
|
||||
}
|
||||
|
||||
function handler(e) {
|
||||
@ -37,12 +39,9 @@ function handler(e) {
|
||||
const preventBubbling = handler();
|
||||
|
||||
if (preventBubbling === undefined || preventBubbling === true) {
|
||||
if (!e.isDefaultPrevented()) {
|
||||
e.preventDefault();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
src/performance.js
Normal file
14
src/performance.js
Normal file
@ -0,0 +1,14 @@
|
||||
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;
|
||||
120
src/rowmanager.js
Normal file
120
src/rowmanager.js
Normal file
@ -0,0 +1,120 @@
|
||||
import $ from './dom';
|
||||
|
||||
export default class RowManager {
|
||||
constructor(instance) {
|
||||
this.instance = instance;
|
||||
this.options = this.instance.options;
|
||||
this.wrapper = this.instance.wrapper;
|
||||
this.datamanager = this.instance.datamanager;
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
getCheckedRows() {
|
||||
return this.checkMap
|
||||
.map((c, rowIndex) => {
|
||||
if (c) {
|
||||
return rowIndex;
|
||||
}
|
||||
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 = $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable);
|
||||
|
||||
if (toggle) {
|
||||
$row.classList.add('row-highlight');
|
||||
} else {
|
||||
$row.classList.remove('row-highlight');
|
||||
}
|
||||
}
|
||||
|
||||
highlightAll(toggle = true) {
|
||||
if (toggle) {
|
||||
this.bodyScrollable.classList.add('row-highlight-all');
|
||||
} else {
|
||||
this.bodyScrollable.classList.remove('row-highlight-all');
|
||||
}
|
||||
}
|
||||
|
||||
getTotalRows() {
|
||||
return this.datamanager.getRowCount();
|
||||
}
|
||||
|
||||
getFirstRowIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getLastRowIndex() {
|
||||
return this.datamanager.getRowCount() - 1;
|
||||
}
|
||||
}
|
||||
25
src/style.js
Normal file
25
src/style.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { camelCaseToDash } from './utils';
|
||||
|
||||
export default class Style {
|
||||
|
||||
constructor(wrapper) {
|
||||
const styleEl = document.createElement('style');
|
||||
|
||||
document.head.appendChild(styleEl);
|
||||
this.styleSheet = styleEl.sheet;
|
||||
}
|
||||
|
||||
setStyle(rule, styleMap) {
|
||||
const styles = Object.keys(styleMap)
|
||||
.map(prop => {
|
||||
if (!prop.includes('-')) {
|
||||
prop = camelCaseToDash(prop);
|
||||
}
|
||||
return `${prop}:${styleMap[prop]};`;
|
||||
})
|
||||
.join('');
|
||||
let ruleString = `${rule} { ${styles} }`;
|
||||
|
||||
this.styleSheet.insertRule(ruleString, this.styleSheet.cssRules.length);
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,10 @@ button, input {
|
||||
max-height: 500px;
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
&.row-highlight-all .data-table-row {
|
||||
background-color: var(--light-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.data-table-header {
|
||||
|
||||
@ -255,6 +255,10 @@ function copyTextToClipboard(text) {
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
|
||||
function isNumeric(val) {
|
||||
return !isNaN(val);
|
||||
}
|
||||
|
||||
export default {
|
||||
getHeaderHTML,
|
||||
getBodyHTML,
|
||||
@ -271,5 +275,7 @@ export default {
|
||||
getDefault,
|
||||
escapeRegExp,
|
||||
getCellContent,
|
||||
copyTextToClipboard
|
||||
copyTextToClipboard,
|
||||
camelCaseToDash,
|
||||
isNumeric
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user