Indent using 4 spaces
This commit is contained in:
parent
d8fb48ba91
commit
6ee51038d0
@ -1,99 +1,101 @@
|
|||||||
import $ from './dom';
|
import $ from './dom';
|
||||||
import Clusterize from 'clusterize.js';
|
import Clusterize from 'clusterize.js';
|
||||||
import { promisify } from './utils';
|
import {
|
||||||
|
promisify
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
export default class BodyRenderer {
|
export default class BodyRenderer {
|
||||||
constructor(instance) {
|
constructor(instance) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.options = instance.options;
|
this.options = instance.options;
|
||||||
this.datamanager = instance.datamanager;
|
this.datamanager = instance.datamanager;
|
||||||
this.rowmanager = instance.rowmanager;
|
this.rowmanager = instance.rowmanager;
|
||||||
this.cellmanager = instance.cellmanager;
|
this.cellmanager = instance.cellmanager;
|
||||||
this.bodyScrollable = instance.bodyScrollable;
|
this.bodyScrollable = instance.bodyScrollable;
|
||||||
this.log = instance.log;
|
this.log = instance.log;
|
||||||
this.appendRemainingData = promisify(this.appendRemainingData, this);
|
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 = `
|
|
||||||
<table class="data-table-body">
|
|
||||||
${getBodyHTML(rows)}
|
|
||||||
</table>
|
|
||||||
`;
|
|
||||||
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 = `
|
|
||||||
<table class="data-table-body">
|
|
||||||
${getBodyHTML([])}
|
|
||||||
</table>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 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();
|
render() {
|
||||||
}
|
if (this.options.enableClusterize) {
|
||||||
|
this.renderBodyWithClusterize();
|
||||||
|
} else {
|
||||||
|
this.renderBodyHTML();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
restoreState() {
|
renderBodyHTML() {
|
||||||
this.rowmanager.highlightCheckedRows();
|
const rows = this.datamanager.getRows();
|
||||||
this.cellmanager.selectAreaOnClusterChanged();
|
|
||||||
this.cellmanager.focusCellOnClusterChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
appendRemainingData() {
|
this.bodyScrollable.innerHTML = `
|
||||||
const rows = this.datamanager.getRows(20);
|
<table class="data-table-body">
|
||||||
const data = this.getDataForClusterize(rows);
|
${this.getBodyHTML(rows)}
|
||||||
this.clusterize.append(data);
|
</table>
|
||||||
}
|
`;
|
||||||
|
this.instance.setDimensions();
|
||||||
|
this.restoreState();
|
||||||
|
}
|
||||||
|
|
||||||
getDataForClusterize(rows) {
|
renderBodyWithClusterize() {
|
||||||
return rows.map((row) => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex }));
|
// first page
|
||||||
}
|
const rows = this.datamanager.getRows(0, 20);
|
||||||
|
const initialData = this.getDataForClusterize(rows);
|
||||||
|
|
||||||
|
if (!this.clusterize) {
|
||||||
|
// empty body
|
||||||
|
this.bodyScrollable.innerHTML = `
|
||||||
|
<table class="data-table-body">
|
||||||
|
${this.getBodyHTML([])}
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 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 `
|
||||||
|
<tbody>
|
||||||
|
${rows.map(row => this.rowmanager.getRowHTML(row, row.meta)).join('')}
|
||||||
|
</tbody>
|
||||||
|
`;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getBodyHTML(rows) {
|
|
||||||
return `
|
|
||||||
<tbody>
|
|
||||||
${rows.map(row => this.rowmanager.getRowHTML(row, { rowIndex: row[0].rowIndex })).join('')}
|
|
||||||
</tbody>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|||||||
1166
src/cellmanager.js
1166
src/cellmanager.js
File diff suppressed because it is too large
Load Diff
@ -1,413 +1,452 @@
|
|||||||
import $ from './dom';
|
import $ from './dom';
|
||||||
import Sortable from 'sortablejs';
|
import Sortable from 'sortablejs';
|
||||||
import { getDefault, linkProperties, debounce } from './utils';
|
import {
|
||||||
|
getDefault,
|
||||||
|
linkProperties,
|
||||||
|
debounce
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
export default class ColumnManager {
|
export default class ColumnManager {
|
||||||
constructor(instance) {
|
constructor(instance) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
|
|
||||||
linkProperties(this, this.instance, [
|
linkProperties(this, this.instance, [
|
||||||
'options',
|
'options',
|
||||||
'fireEvent',
|
'fireEvent',
|
||||||
'header',
|
'header',
|
||||||
'datamanager',
|
'datamanager',
|
||||||
'style',
|
'style',
|
||||||
'wrapper',
|
'wrapper',
|
||||||
'rowmanager',
|
'rowmanager',
|
||||||
'bodyScrollable'
|
'bodyScrollable'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
|
getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader() {
|
renderHeader() {
|
||||||
this.header.innerHTML = '<thead></thead>';
|
this.header.innerHTML = '<thead></thead>';
|
||||||
this.refreshHeader();
|
this.refreshHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshHeader() {
|
refreshHeader() {
|
||||||
const columns = this.datamanager.getColumns();
|
const columns = this.datamanager.getColumns();
|
||||||
|
|
||||||
if (!$('.data-table-col', this.header)) {
|
if (!$('.data-table-col', this.header)) {
|
||||||
// insert html
|
// insert html
|
||||||
|
|
||||||
let html = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
|
let html = this.rowmanager.getRowHTML(columns, {
|
||||||
if (this.options.enableInlineFilters) {
|
isHeader: 1
|
||||||
html += this.rowmanager.getRowHTML(columns, { isFilter: 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) {
|
if (this.$filterRow) {
|
||||||
// hide filter row immediately, so it doesn't disturb layout
|
// hide filter row immediately, so it doesn't disturb layout
|
||||||
$.style(this.$filterRow, {
|
$.style(this.$filterRow, {
|
||||||
display: 'none'
|
display: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// refresh dom state
|
// refresh dom state
|
||||||
const $cols = $.each('.data-table-col', this.header);
|
const $cols = $.each('.data-table-col', this.header);
|
||||||
if (columns.length < $cols.length) {
|
if (columns.length < $cols.length) {
|
||||||
// deleted column
|
// deleted column
|
||||||
$('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, { isHeader: 1 });
|
$('thead', this.header).innerHTML = this.rowmanager.getRowHTML(columns, {
|
||||||
return;
|
isHeader: 1
|
||||||
}
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$cols.map(($col, i) => {
|
$cols.map(($col, i) => {
|
||||||
const column = columns[i];
|
const column = columns[i];
|
||||||
// column sorted or order changed
|
// column sorted or order changed
|
||||||
// update colIndex of each header cell
|
// update colIndex of each header cell
|
||||||
$.data($col, {
|
$.data($col, {
|
||||||
colIndex: column.colIndex
|
colIndex: column.colIndex
|
||||||
});
|
});
|
||||||
|
|
||||||
// refresh sort indicator
|
// refresh sort indicator
|
||||||
const sortIndicator = $('.sort-indicator', $col);
|
const sortIndicator = $('.sort-indicator', $col);
|
||||||
if (sortIndicator) {
|
if (sortIndicator) {
|
||||||
sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
|
sortIndicator.innerHTML = this.options.sortIndicator[column.sortOrder];
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
// reset columnMap
|
||||||
|
this.$columnMap = [];
|
||||||
}
|
}
|
||||||
// reset columnMap
|
|
||||||
this.$columnMap = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
this.bindDropdown();
|
this.bindDropdown();
|
||||||
this.bindResizeColumn();
|
this.bindResizeColumn();
|
||||||
this.bindMoveColumn();
|
this.bindMoveColumn();
|
||||||
this.bindFilter();
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bindResizeColumn() {
|
bindDropdown() {
|
||||||
let isDragging = false;
|
let $activeDropdown;
|
||||||
let $resizingCell, startWidth, startX;
|
$.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) => {
|
if (!$dropdown.classList.contains('is-active')) {
|
||||||
document.body.classList.add('data-table-resize');
|
deactivateDropdown();
|
||||||
const $cell = $handle.parentNode.parentNode;
|
$dropdown.classList.add('is-active');
|
||||||
$resizingCell = $cell;
|
$activeDropdown = $dropdown;
|
||||||
const { colIndex } = $.data($resizingCell);
|
} else {
|
||||||
const col = this.getColumn(colIndex);
|
deactivateDropdown();
|
||||||
|
}
|
||||||
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'
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
let nextSortOrder, textContent;
|
$.on(document.body, 'click', (e) => {
|
||||||
if (sortOrder === 'none') {
|
if (e.target.matches('.data-table-dropdown-toggle')) return;
|
||||||
nextSortOrder = 'asc';
|
deactivateDropdown();
|
||||||
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) {
|
const dropdownItems = this.options.headerDropdown;
|
||||||
return this.datamanager.sortRows(colIndex, sortOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
getColumn(colIndex) {
|
$.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => {
|
||||||
return this.datamanager.getColumn(colIndex);
|
const $col = $.closest('.data-table-col', $item);
|
||||||
}
|
const {
|
||||||
|
index
|
||||||
|
} = $.data($item);
|
||||||
|
const {
|
||||||
|
colIndex
|
||||||
|
} = $.data($col);
|
||||||
|
let callback = dropdownItems[index].action;
|
||||||
|
|
||||||
getColumns() {
|
callback && callback.call(this.instance, this.getColumn(colIndex));
|
||||||
return this.datamanager.getColumns();
|
});
|
||||||
}
|
|
||||||
|
|
||||||
setColumnWidth(colIndex) {
|
function deactivateDropdown(e) {
|
||||||
colIndex = +colIndex;
|
$activeDropdown && $activeDropdown.classList.remove('is-active');
|
||||||
this._columnWidthMap = this._columnWidthMap || [];
|
$activeDropdown = null;
|
||||||
|
}
|
||||||
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';
|
bindResizeColumn() {
|
||||||
}
|
let isDragging = false;
|
||||||
|
let $resizingCell, startWidth, startX;
|
||||||
|
|
||||||
getColumnMinWidth(colIndex) {
|
$.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
|
||||||
colIndex = +colIndex;
|
document.body.classList.add('data-table-resize');
|
||||||
return this.getColumn(colIndex).minWidth || 24;
|
const $cell = $handle.parentNode.parentNode;
|
||||||
}
|
$resizingCell = $cell;
|
||||||
|
const {
|
||||||
|
colIndex
|
||||||
|
} = $.data($resizingCell);
|
||||||
|
const col = this.getColumn(colIndex);
|
||||||
|
|
||||||
getFirstColumnIndex() {
|
if (col && col.resizable === false) {
|
||||||
if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
|
return;
|
||||||
return 2;
|
}
|
||||||
|
|
||||||
|
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) {
|
bindMoveColumn() {
|
||||||
return 1;
|
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) {
|
$.on(this.header, 'click', '.data-table-col .column-title', (e, span) => {
|
||||||
return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
|
const $cell = span.closest('.data-table-col');
|
||||||
}
|
let {
|
||||||
|
colIndex,
|
||||||
|
sortOrder
|
||||||
|
} = $.data($cell);
|
||||||
|
sortOrder = getDefault(sortOrder, 'none');
|
||||||
|
const col = this.getColumn(colIndex);
|
||||||
|
|
||||||
getLastColumnIndex() {
|
if (col && col.sortable === false) {
|
||||||
return this.datamanager.getColumnCount() - 1;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSerialColumnIndex() {
|
// reset sort indicator
|
||||||
const columns = this.datamanager.getColumns();
|
$('.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
|
// eslint-disable-next-line
|
||||||
var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
|
var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
|
||||||
// add dropdown buttons
|
// add dropdown buttons
|
||||||
const dropdownItems = this.options.headerDropdown;
|
const dropdownItems = this.options.headerDropdown;
|
||||||
|
|
||||||
return `<div class="data-table-dropdown-toggle">${dropdownButton}</div>
|
return `<div class="data-table-dropdown-toggle">${dropdownButton}</div>
|
||||||
<div class="data-table-dropdown-list">
|
<div class="data-table-dropdown-list">
|
||||||
${dropdownItems.map((d, i) => `<div data-index="${i}">${d.label}</div>`).join('')}
|
${dropdownItems.map((d, i) => `<div data-index="${i}">${d.label}</div>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
@ -415,5 +454,5 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getDropdownHTML
|
getDropdownHTML
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,500 +1,518 @@
|
|||||||
import { isNumeric, promisify } from './utils';
|
import {
|
||||||
|
isNumeric,
|
||||||
|
promisify
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
export default class DataManager {
|
export default class DataManager {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.sortRows = promisify(this.sortRows, this);
|
this.sortRows = promisify(this.sortRows, this);
|
||||||
this.switchColumn = promisify(this.switchColumn, this);
|
this.switchColumn = promisify(this.switchColumn, this);
|
||||||
this.removeColumn = promisify(this.removeColumn, this);
|
this.removeColumn = promisify(this.removeColumn, this);
|
||||||
this.filterRows = promisify(this.filterRows, this);
|
this.filterRows = promisify(this.filterRows, this);
|
||||||
}
|
|
||||||
|
|
||||||
init(data) {
|
|
||||||
if (!data) {
|
|
||||||
data = this.options.data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data = data;
|
init(data) {
|
||||||
|
if (!data) {
|
||||||
this.rowCount = 0;
|
data = this.options.data;
|
||||||
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('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
this.data = data;
|
||||||
// row is a dict
|
|
||||||
for (let col of this.columns) {
|
this.rowCount = 0;
|
||||||
if (col.id === '_checkbox') {
|
this.columns = [];
|
||||||
row.push(this.getCheckboxHTML());
|
this.rows = [];
|
||||||
} else if (col.id === '_rowIndex') {
|
|
||||||
row.push((index + 1) + '');
|
this.prepareColumns();
|
||||||
} else {
|
this.prepareRows();
|
||||||
row.push(d[col.id]);
|
|
||||||
}
|
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() {
|
this.columns.push(cell);
|
||||||
const columns = this.options.columns;
|
}
|
||||||
if (!Array.isArray(columns)) {
|
|
||||||
throw new DataError('`columns` must be an array');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.forEach((column, i) => {
|
prepareHeader() {
|
||||||
if (typeof column !== 'string' && typeof column !== 'object') {
|
let columns = this.columns.concat(this.options.columns);
|
||||||
throw new DataError(`column "${i}" must be a string or an object`);
|
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) {
|
this.columns = columns
|
||||||
if (Array.isArray(data) &&
|
.map((cell, i) => this.prepareCell(cell, i))
|
||||||
(data.length === 0 || Array.isArray(data[0]) || typeof data[0] === 'object')) {
|
.map(col => Object.assign({}, baseCell, col))
|
||||||
return true;
|
.map(col => {
|
||||||
|
col.id = col.id || col.content;
|
||||||
|
return col;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
throw new DataError('`data` must be an array of arrays or objects');
|
|
||||||
}
|
|
||||||
|
|
||||||
appendRows(rows) {
|
prepareCell(content, i) {
|
||||||
this.validateData(rows);
|
const cell = {
|
||||||
|
content: '',
|
||||||
|
align: 'left',
|
||||||
|
sortOrder: 'none',
|
||||||
|
colIndex: i,
|
||||||
|
column: this.columns[i]
|
||||||
|
};
|
||||||
|
|
||||||
this.rows = this.rows.concat(this.prepareRows(rows));
|
if (content !== null && typeof content === 'object') {
|
||||||
}
|
// passed as column/header
|
||||||
|
Object.assign(cell, content);
|
||||||
sortRows(colIndex, sortOrder = 'none') {
|
|
||||||
colIndex = +colIndex;
|
|
||||||
|
|
||||||
// reset sortOrder and update for colIndex
|
|
||||||
this.getColumns()
|
|
||||||
.map(col => {
|
|
||||||
if (col.colIndex === colIndex) {
|
|
||||||
col.sortOrder = sortOrder;
|
|
||||||
} else {
|
} else {
|
||||||
col.sortOrder = 'none';
|
cell.content = content;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
this._sortRows(colIndex, sortOrder);
|
return cell;
|
||||||
}
|
|
||||||
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rows.sort((a, b) => {
|
prepareNumericColumns() {
|
||||||
const _aIndex = a[0].rowIndex;
|
const row0 = this.getRow(0);
|
||||||
const _bIndex = b[0].rowIndex;
|
if (!row0) return;
|
||||||
const _a = a[colIndex].content;
|
this.columns = this.columns.map((column, i) => {
|
||||||
const _b = b[colIndex].content;
|
|
||||||
|
|
||||||
if (sortOrder === 'none') {
|
const cellValue = row0[i].content;
|
||||||
return _aIndex - _bIndex;
|
if (!column.align && cellValue && isNumeric(cellValue)) {
|
||||||
} else if (sortOrder === 'asc') {
|
column.align = 'right';
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.hasColumnById('_rowIndex')) {
|
return column;
|
||||||
// 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;
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = '<input type="checkbox" />';
|
|
||||||
|
|
||||||
row = [val].concat(row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _row = this.prepareRow(row, rowIndex);
|
prepareRows() {
|
||||||
const index = this.rows.findIndex(row => row[0].rowIndex === rowIndex);
|
this.validateData(this.data);
|
||||||
this.rows[index] = _row;
|
|
||||||
|
|
||||||
return _row;
|
this.rows = this.data.map((d, i) => {
|
||||||
}
|
const index = this._getNextRowCount();
|
||||||
|
|
||||||
updateCell(colIndex, rowIndex, options) {
|
let row = [];
|
||||||
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
|
if (Array.isArray(d)) {
|
||||||
for (let key in options) {
|
// row is an array
|
||||||
const newVal = options[key];
|
if (this.options.addCheckboxColumn) {
|
||||||
if (newVal !== undefined) {
|
row.push(this.getCheckboxHTML());
|
||||||
cell[key] = newVal;
|
}
|
||||||
}
|
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) {
|
row = row
|
||||||
const column = this.getColumn(colIndex);
|
.map((cell, i) => this.prepareCell(cell, i))
|
||||||
for (let key in keyValPairs) {
|
.map(cell => Object.assign({}, baseRowCell, cell));
|
||||||
const newVal = keyValPairs[key];
|
|
||||||
if (newVal !== undefined) {
|
|
||||||
column[key] = newVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return column;
|
|
||||||
}
|
|
||||||
|
|
||||||
filterRows(keyword, colIndex) {
|
// monkey patched in array object
|
||||||
let rowsToHide = [];
|
row.meta = props;
|
||||||
let rowsToShow = [];
|
return row;
|
||||||
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;
|
validateColumns() {
|
||||||
}
|
const columns = this.options.columns;
|
||||||
|
if (!Array.isArray(columns)) {
|
||||||
|
throw new DataError('`columns` must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
getStandardColumnCount() {
|
columns.forEach((column, i) => {
|
||||||
if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
|
if (typeof column !== 'string' && typeof column !== 'object') {
|
||||||
return 2;
|
throw new DataError(`column "${i}" must be a string or an object`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
|
validateData(data) {
|
||||||
return 1;
|
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) {
|
this.rows = this.rows.concat(this.prepareRows(rows));
|
||||||
let val = this.columns.length;
|
|
||||||
|
|
||||||
if (skipStandardColumns) {
|
|
||||||
val = val - this.getStandardColumnCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return val;
|
sortRows(colIndex, sortOrder = 'none') {
|
||||||
}
|
colIndex = +colIndex;
|
||||||
|
|
||||||
getColumn(colIndex) {
|
// reset sortOrder and update for colIndex
|
||||||
colIndex = +colIndex;
|
this.getColumns()
|
||||||
return this.columns.find(col => col.colIndex === colIndex);
|
.map(col => {
|
||||||
}
|
if (col.colIndex === colIndex) {
|
||||||
|
col.sortOrder = sortOrder;
|
||||||
|
} else {
|
||||||
|
col.sortOrder = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
getRow(rowIndex) {
|
this._sortRows(colIndex, sortOrder);
|
||||||
rowIndex = +rowIndex;
|
}
|
||||||
return this.rows.find(row => row[0].rowIndex === rowIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCell(colIndex, rowIndex) {
|
_sortRows(colIndex, sortOrder) {
|
||||||
rowIndex = +rowIndex;
|
|
||||||
colIndex = +colIndex;
|
|
||||||
return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
get() {
|
if (this.currentSort.colIndex === colIndex) {
|
||||||
return {
|
// reverse the array if only sortOrder changed
|
||||||
columns: this.columns,
|
if (
|
||||||
rows: this.rows
|
(this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') ||
|
||||||
};
|
(this.currentSort.sortOrder === 'desc' && sortOrder === 'asc')
|
||||||
}
|
) {
|
||||||
|
this.reverseArray(this.rows);
|
||||||
|
this.currentSort.sortOrder = sortOrder;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasColumn(name) {
|
this.rows.sort((a, b) => {
|
||||||
return Boolean(this.columns.find(col => col.content === name));
|
const _aIndex = a[0].rowIndex;
|
||||||
}
|
const _bIndex = b[0].rowIndex;
|
||||||
|
const _a = a[colIndex].content;
|
||||||
|
const _b = b[colIndex].content;
|
||||||
|
|
||||||
hasColumnById(id) {
|
if (sortOrder === 'none') {
|
||||||
return Boolean(this.columns.find(col => col.id === id));
|
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) {
|
if (this.hasColumnById('_rowIndex')) {
|
||||||
return this.columns.findIndex(col => col.content === name);
|
// 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) {
|
reverseArray(array) {
|
||||||
return this.columns.findIndex(col => col.id === id);
|
let left = null;
|
||||||
}
|
let right = null;
|
||||||
|
let length = array.length;
|
||||||
|
|
||||||
getCheckboxHTML() {
|
for (left = 0, right = length - 1; left < right; left += 1, right -= 1) {
|
||||||
return '<input type="checkbox" />';
|
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 = '<input type="checkbox" />';
|
||||||
|
|
||||||
|
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 '<input type="checkbox" />';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom Errors
|
// Custom Errors
|
||||||
|
|||||||
278
src/datatable.js
278
src/datatable.js
@ -10,50 +10,48 @@ import DEFAULT_OPTIONS from './defaults';
|
|||||||
import './style.css';
|
import './style.css';
|
||||||
|
|
||||||
class DataTable {
|
class DataTable {
|
||||||
constructor(wrapper, options) {
|
constructor(wrapper, options) {
|
||||||
DataTable.instances++;
|
DataTable.instances++;
|
||||||
|
|
||||||
if (typeof wrapper === 'string') {
|
if (typeof wrapper === 'string') {
|
||||||
// css selector
|
// css selector
|
||||||
wrapper = document.querySelector(wrapper);
|
wrapper = document.querySelector(wrapper);
|
||||||
}
|
}
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
if (!(this.wrapper instanceof HTMLElement)) {
|
if (!(this.wrapper instanceof HTMLElement)) {
|
||||||
throw new Error('Invalid argument given for `wrapper`');
|
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);
|
prepare() {
|
||||||
this.options.headerDropdown =
|
this.prepareDom();
|
||||||
DEFAULT_OPTIONS.headerDropdown
|
this.unfreeze();
|
||||||
.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() {
|
prepareDom() {
|
||||||
this.prepareDom();
|
this.wrapper.innerHTML = `
|
||||||
this.unfreeze();
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareDom() {
|
|
||||||
this.wrapper.innerHTML = `
|
|
||||||
<div class="data-table">
|
<div class="data-table">
|
||||||
<table class="data-table-header">
|
<table class="data-table-header">
|
||||||
</table>
|
</table>
|
||||||
@ -67,110 +65,110 @@ class DataTable {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this.datatableWrapper = $('.data-table', this.wrapper);
|
this.datatableWrapper = $('.data-table', this.wrapper);
|
||||||
this.header = $('.data-table-header', this.wrapper);
|
this.header = $('.data-table-header', this.wrapper);
|
||||||
this.bodyScrollable = $('.body-scrollable', this.wrapper);
|
this.bodyScrollable = $('.body-scrollable', this.wrapper);
|
||||||
this.freezeContainer = $('.freeze-container', 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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.viewportHeight;
|
refresh(data) {
|
||||||
}
|
this.datamanager.init(data);
|
||||||
|
this.render();
|
||||||
sortColumn(colIndex, sortOrder) {
|
this.setDimensions();
|
||||||
this.columnmanager.sortColumn(colIndex, sortOrder);
|
}
|
||||||
}
|
|
||||||
|
destroy() {
|
||||||
removeColumn(colIndex) {
|
this.wrapper.innerHTML = '';
|
||||||
this.columnmanager.removeColumn(colIndex);
|
this.style.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToLastColumn() {
|
appendRows(rows) {
|
||||||
this.datatableWrapper.scrollLeft = 9999;
|
this.datamanager.appendRows(rows);
|
||||||
}
|
this.rowmanager.refreshRows();
|
||||||
|
}
|
||||||
freeze() {
|
|
||||||
$.style(this.freezeContainer, {
|
refreshRow(row, rowIndex) {
|
||||||
display: ''
|
this.rowmanager.refreshRow(row, rowIndex);
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
render() {
|
||||||
unfreeze() {
|
this.renderHeader();
|
||||||
$.style(this.freezeContainer, {
|
this.renderBody();
|
||||||
display: 'none'
|
}
|
||||||
});
|
|
||||||
}
|
renderHeader() {
|
||||||
|
this.columnmanager.renderHeader();
|
||||||
fireEvent(eventName, ...args) {
|
}
|
||||||
this.events[eventName].apply(this, args);
|
|
||||||
}
|
renderBody() {
|
||||||
|
this.bodyRenderer.render();
|
||||||
log() {
|
}
|
||||||
if (this.options.enableLogs) {
|
|
||||||
console.log.apply(console, arguments);
|
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;
|
DataTable.instances = 0;
|
||||||
|
|||||||
@ -1,51 +1,51 @@
|
|||||||
export default {
|
export default {
|
||||||
columns: [],
|
columns: [],
|
||||||
data: [],
|
data: [],
|
||||||
dropdownButton: '▼',
|
dropdownButton: '▼',
|
||||||
headerDropdown: [
|
headerDropdown: [
|
||||||
{
|
{
|
||||||
label: 'Sort Ascending',
|
label: 'Sort Ascending',
|
||||||
action: function (column) {
|
action: function (column) {
|
||||||
this.sortColumn(column.colIndex, 'asc');
|
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) {}
|
||||||
},
|
},
|
||||||
{
|
sortIndicator: {
|
||||||
label: 'Sort Descending',
|
asc: '↑',
|
||||||
action: function (column) {
|
desc: '↓',
|
||||||
this.sortColumn(column.colIndex, 'desc');
|
none: ''
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
freezeMessage: '',
|
||||||
label: 'Reset sorting',
|
getEditor: () => {},
|
||||||
action: function (column) {
|
addSerialNoColumn: true,
|
||||||
this.sortColumn(column.colIndex, 'none');
|
addCheckboxColumn: false,
|
||||||
}
|
enableClusterize: true,
|
||||||
},
|
enableLogs: false,
|
||||||
{
|
layout: 'fixed', // fixed, fluid
|
||||||
label: 'Remove column',
|
noDataMessage: 'No Data',
|
||||||
action: function (column) {
|
cellHeight: null,
|
||||||
this.removeColumn(column.colIndex);
|
enableInlineFilters: false
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
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
|
|
||||||
};
|
};
|
||||||
|
|||||||
219
src/dom.js
219
src/dom.js
@ -1,172 +1,181 @@
|
|||||||
|
|
||||||
export default function $(expr, con) {
|
export default function $(expr, con) {
|
||||||
return typeof expr === 'string' ?
|
return typeof expr === 'string' ?
|
||||||
(con || document).querySelector(expr) :
|
(con || document).querySelector(expr) :
|
||||||
expr || null;
|
expr || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.each = (expr, con) => {
|
$.each = (expr, con) => {
|
||||||
return typeof expr === 'string' ?
|
return typeof expr === 'string' ?
|
||||||
Array.from((con || document).querySelectorAll(expr)) :
|
Array.from((con || document).querySelectorAll(expr)) :
|
||||||
expr || null;
|
expr || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
$.create = (tag, o) => {
|
$.create = (tag, o) => {
|
||||||
let element = document.createElement(tag);
|
let element = document.createElement(tag);
|
||||||
|
|
||||||
for (let i in o) {
|
for (let i in o) {
|
||||||
let val = o[i];
|
let val = o[i];
|
||||||
|
|
||||||
if (i === 'inside') {
|
if (i === 'inside') {
|
||||||
$(val).appendChild(element);
|
$(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
|
} 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;
|
element[i] = val;
|
||||||
} else {
|
} else {
|
||||||
element.setAttribute(i, val);
|
element.setAttribute(i, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
|
||||||
$.on = (element, event, selector, callback) => {
|
$.on = (element, event, selector, callback) => {
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
callback = selector;
|
callback = selector;
|
||||||
$.bind(element, event, callback);
|
$.bind(element, event, callback);
|
||||||
} else {
|
} else {
|
||||||
$.delegate(element, event, selector, callback);
|
$.delegate(element, event, selector, callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$.off = (element, event, handler) => {
|
$.off = (element, event, handler) => {
|
||||||
element.removeEventListener(event, handler);
|
element.removeEventListener(event, handler);
|
||||||
};
|
};
|
||||||
|
|
||||||
$.bind = (element, event, callback) => {
|
$.bind = (element, event, callback) => {
|
||||||
event.split(/\s+/).forEach(function (event) {
|
event.split(/\s+/).forEach(function (event) {
|
||||||
element.addEventListener(event, callback);
|
element.addEventListener(event, callback);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$.delegate = (element, event, selector, callback) => {
|
$.delegate = (element, event, selector, callback) => {
|
||||||
element.addEventListener(event, function (e) {
|
element.addEventListener(event, function (e) {
|
||||||
const delegatedTarget = e.target.closest(selector);
|
const delegatedTarget = e.target.closest(selector);
|
||||||
if (delegatedTarget) {
|
if (delegatedTarget) {
|
||||||
e.delegatedTarget = delegatedTarget;
|
e.delegatedTarget = delegatedTarget;
|
||||||
callback.call(this, e, delegatedTarget);
|
callback.call(this, e, delegatedTarget);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$.unbind = (element, o) => {
|
$.unbind = (element, o) => {
|
||||||
if (element) {
|
if (element) {
|
||||||
for (let event in o) {
|
for (let event in o) {
|
||||||
let callback = o[event];
|
let callback = o[event];
|
||||||
|
|
||||||
event.split(/\s+/).forEach(function (event) {
|
event.split(/\s+/).forEach(function (event) {
|
||||||
element.removeEventListener(event, callback);
|
element.removeEventListener(event, callback);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$.fire = (target, type, properties) => {
|
$.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) {
|
for (let j in properties) {
|
||||||
evt[j] = properties[j];
|
evt[j] = properties[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
return target.dispatchEvent(evt);
|
return target.dispatchEvent(evt);
|
||||||
};
|
};
|
||||||
|
|
||||||
$.data = (element, attrs) => { // eslint-disable-line
|
$.data = (element, attrs) => { // eslint-disable-line
|
||||||
if (!attrs) {
|
if (!attrs) {
|
||||||
return element.dataset;
|
return element.dataset;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const attr in attrs) {
|
for (const attr in attrs) {
|
||||||
element.dataset[attr] = attrs[attr];
|
element.dataset[attr] = attrs[attr];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$.style = (elements, styleMap) => { // eslint-disable-line
|
$.style = (elements, styleMap) => { // eslint-disable-line
|
||||||
|
|
||||||
if (typeof styleMap === 'string') {
|
if (typeof styleMap === 'string') {
|
||||||
return $.getStyle(elements, styleMap);
|
return $.getStyle(elements, styleMap);
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(elements)) {
|
|
||||||
elements = [elements];
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.map(element => {
|
|
||||||
for (const prop in styleMap) {
|
|
||||||
element.style[prop] = styleMap[prop];
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
if (!Array.isArray(elements)) {
|
||||||
|
elements = [elements];
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.map(element => {
|
||||||
|
for (const prop in styleMap) {
|
||||||
|
element.style[prop] = styleMap[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$.removeStyle = (elements, styleProps) => {
|
$.removeStyle = (elements, styleProps) => {
|
||||||
if (!Array.isArray(elements)) {
|
if (!Array.isArray(elements)) {
|
||||||
elements = [elements];
|
elements = [elements];
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(styleProps)) {
|
|
||||||
styleProps = [styleProps];
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.map(element => {
|
|
||||||
for (const prop of styleProps) {
|
|
||||||
element.style[prop] = '';
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
if (!Array.isArray(styleProps)) {
|
||||||
|
styleProps = [styleProps];
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.map(element => {
|
||||||
|
for (const prop of styleProps) {
|
||||||
|
element.style[prop] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$.getStyle = (element, prop) => {
|
$.getStyle = (element, prop) => {
|
||||||
let val = getComputedStyle(element)[prop];
|
let val = getComputedStyle(element)[prop];
|
||||||
|
|
||||||
if (['width', 'height'].includes(prop)) {
|
if (['width', 'height'].includes(prop)) {
|
||||||
val = parseFloat(val);
|
val = parseFloat(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
|
||||||
$.closest = (selector, element) => {
|
$.closest = (selector, element) => {
|
||||||
if (!element) return null;
|
if (!element) return null;
|
||||||
|
|
||||||
if (element.matches(selector)) {
|
if (element.matches(selector)) {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $.closest(selector, element.parentNode);
|
return $.closest(selector, element.parentNode);
|
||||||
};
|
};
|
||||||
|
|
||||||
$.inViewport = (el, parentEl) => {
|
$.inViewport = (el, parentEl) => {
|
||||||
const { top, left, bottom, right } = el.getBoundingClientRect();
|
const {
|
||||||
const { top: pTop, left: pLeft, bottom: pBottom, right: pRight } = parentEl.getBoundingClientRect();
|
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) {
|
$.scrollTop = function scrollTop(element, pixels) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
element.scrollTop = pixels;
|
element.scrollTop = pixels;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,56 +1,56 @@
|
|||||||
import $ from './dom';
|
import $ from './dom';
|
||||||
|
|
||||||
const KEYCODES = {
|
const KEYCODES = {
|
||||||
13: 'enter',
|
13: 'enter',
|
||||||
91: 'meta',
|
91: 'meta',
|
||||||
16: 'shift',
|
16: 'shift',
|
||||||
17: 'ctrl',
|
17: 'ctrl',
|
||||||
18: 'alt',
|
18: 'alt',
|
||||||
37: 'left',
|
37: 'left',
|
||||||
38: 'up',
|
38: 'up',
|
||||||
39: 'right',
|
39: 'right',
|
||||||
40: 'down',
|
40: 'down',
|
||||||
9: 'tab',
|
9: 'tab',
|
||||||
27: 'esc',
|
27: 'esc',
|
||||||
67: 'c',
|
67: 'c',
|
||||||
70: 'f'
|
70: 'f'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Keyboard {
|
export default class Keyboard {
|
||||||
constructor(element) {
|
constructor(element) {
|
||||||
this.listeners = {};
|
this.listeners = {};
|
||||||
$.on(element, 'keydown', this.handler.bind(this));
|
$.on(element, 'keydown', this.handler.bind(this));
|
||||||
}
|
|
||||||
|
|
||||||
handler(e) {
|
|
||||||
let key = KEYCODES[e.keyCode];
|
|
||||||
|
|
||||||
if (e.shiftKey && key !== 'shift') {
|
|
||||||
key = 'shift+' + key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
|
handler(e) {
|
||||||
key = 'ctrl+' + key;
|
let key = KEYCODES[e.keyCode];
|
||||||
}
|
|
||||||
|
|
||||||
const listeners = this.listeners[key];
|
if (e.shiftKey && key !== 'shift') {
|
||||||
|
key = 'shift+' + key;
|
||||||
if (listeners && listeners.length > 0) {
|
}
|
||||||
for (let listener of listeners) {
|
|
||||||
const preventBubbling = listener(e);
|
if ((e.ctrlKey && key !== 'ctrl') || (e.metaKey && key !== 'meta')) {
|
||||||
if (preventBubbling === undefined || preventBubbling === true) {
|
key = 'ctrl+' + key;
|
||||||
e.preventDefault();
|
}
|
||||||
|
|
||||||
|
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) {
|
on(key, listener) {
|
||||||
const keys = key.split(',').map(k => k.trim());
|
const keys = key.split(',').map(k => k.trim());
|
||||||
|
|
||||||
keys.map(key => {
|
keys.map(key => {
|
||||||
this.listeners[key] = this.listeners[key] || [];
|
this.listeners[key] = this.listeners[key] || [];
|
||||||
this.listeners[key].push(listener);
|
this.listeners[key].push(listener);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -1,210 +1,224 @@
|
|||||||
import $ from './dom';
|
import $ from './dom';
|
||||||
import { makeDataAttributeString, promisify } from './utils';
|
import {
|
||||||
|
makeDataAttributeString,
|
||||||
|
promisify
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
export default class RowManager {
|
export default class RowManager {
|
||||||
constructor(instance) {
|
constructor(instance) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.options = this.instance.options;
|
this.options = this.instance.options;
|
||||||
this.wrapper = this.instance.wrapper;
|
this.wrapper = this.instance.wrapper;
|
||||||
this.bodyScrollable = this.instance.bodyScrollable;
|
this.bodyScrollable = this.instance.bodyScrollable;
|
||||||
|
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.refreshRows = promisify(this.refreshRows, this);
|
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 [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.checkMap
|
get datamanager() {
|
||||||
.map((c, rowIndex) => {
|
return this.instance.datamanager;
|
||||||
if (c) {
|
}
|
||||||
return rowIndex;
|
|
||||||
|
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() {
|
return this.checkMap
|
||||||
this.getCheckedRows()
|
.map((c, rowIndex) => {
|
||||||
.map(rowIndex => this.checkRow(rowIndex, true));
|
if (c) {
|
||||||
}
|
return rowIndex;
|
||||||
|
}
|
||||||
checkRow(rowIndex, toggle) {
|
return null;
|
||||||
const value = toggle ? 1 : 0;
|
})
|
||||||
|
.filter(c => {
|
||||||
// update internal map
|
return c !== null || c !== undefined;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toggle && $row.classList.contains('row-unhighlight')) {
|
highlightCheckedRows() {
|
||||||
$row.classList.remove('row-unhighlight');
|
this.getCheckedRows()
|
||||||
|
.map(rowIndex => this.checkRow(rowIndex, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._highlightedRows = this._highlightedRows || {};
|
checkRow(rowIndex, toggle) {
|
||||||
|
const value = toggle ? 1 : 0;
|
||||||
if (toggle) {
|
const selector = rowIndex =>
|
||||||
$row.classList.add('row-highlight');
|
`.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`;
|
||||||
this._highlightedRows[rowIndex] = $row;
|
// update internal map
|
||||||
} else {
|
this.checkMap[rowIndex] = value;
|
||||||
$row.classList.remove('row-highlight');
|
// set checkbox value explicitly
|
||||||
delete this._highlightedRows[rowIndex];
|
$.each(selector(rowIndex), this.bodyScrollable)
|
||||||
}
|
.map(input => {
|
||||||
}
|
input.checked = toggle;
|
||||||
|
});
|
||||||
highlightAll(toggle = true) {
|
// highlight row
|
||||||
if (toggle) {
|
this.highlightRow(rowIndex, 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;
|
checkAll(toggle) {
|
||||||
$.scrollTop(this.bodyScrollable, offset);
|
const value = toggle ? 1 : 0;
|
||||||
}
|
|
||||||
|
|
||||||
getRowHTML(row, props) {
|
// update internal map
|
||||||
const dataAttr = makeDataAttributeString(props);
|
if (toggle) {
|
||||||
|
this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value);
|
||||||
if (props.isFilter) {
|
} else {
|
||||||
row = row.map(cell => (Object.assign(cell, {
|
this.checkMap = [];
|
||||||
content: this.getFilterInput({ colIndex: cell.colIndex }),
|
}
|
||||||
isFilter: 1,
|
// set checkbox value
|
||||||
isHeader: undefined,
|
$.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable)
|
||||||
editable: false
|
.map(input => {
|
||||||
})));
|
input.checked = toggle;
|
||||||
|
});
|
||||||
|
// highlight all
|
||||||
|
this.highlightAll(toggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
highlightRow(rowIndex, toggle = true) {
|
||||||
<tr class="data-table-row" ${dataAttr}>
|
const $row = this.getRow$(rowIndex);
|
||||||
${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
|
if (!$row) return;
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilterInput(props) {
|
if (!toggle && this.bodyScrollable.classList.contains('row-highlight-all')) {
|
||||||
const dataAttr = makeDataAttributeString(props);
|
$row.classList.add('row-unhighlight');
|
||||||
return `<input class="data-table-filter input-style" type="text" ${dataAttr} />`;
|
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 `
|
||||||
|
<tr class="data-table-row" ${dataAttr}>
|
||||||
|
${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')}
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterInput(props) {
|
||||||
|
const dataAttr = makeDataAttributeString(props);
|
||||||
|
return `<input class="data-table-filter input-style" type="text" ${dataAttr} />`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
/* This file is processed by postcss */
|
||||||
/* variables */
|
/* variables */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export default class Style {
|
|||||||
this.styleEl.remove();
|
this.styleEl.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
setStyle(rule, styleMap, index = -1) {
|
setStyle(selector, styleMap, index = -1) {
|
||||||
const styles = Object.keys(styleMap)
|
const styles = Object.keys(styleMap)
|
||||||
.map(prop => {
|
.map(prop => {
|
||||||
if (!prop.includes('-')) {
|
if (!prop.includes('-')) {
|
||||||
@ -48,7 +48,12 @@ export default class Style {
|
|||||||
return `${prop}:${styleMap[prop]};`;
|
return `${prop}:${styleMap[prop]};`;
|
||||||
})
|
})
|
||||||
.join('');
|
.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;
|
let _index = this.styleSheet.cssRules.length;
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
|||||||
232
src/utils.js
232
src/utils.js
@ -2,153 +2,153 @@ import _throttle from 'lodash/throttle';
|
|||||||
import _debounce from 'lodash/debounce';
|
import _debounce from 'lodash/debounce';
|
||||||
|
|
||||||
export function camelCaseToDash(str) {
|
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) {
|
export function makeDataAttributeString(props) {
|
||||||
const keys = Object.keys(props);
|
const keys = Object.keys(props);
|
||||||
|
|
||||||
return keys
|
return keys
|
||||||
.map((key) => {
|
.map((key) => {
|
||||||
const _key = camelCaseToDash(key);
|
const _key = camelCaseToDash(key);
|
||||||
const val = props[key];
|
const val = props[key];
|
||||||
|
|
||||||
if (val === undefined) return '';
|
if (val === undefined) return '';
|
||||||
return `data-${_key}="${val}" `;
|
return `data-${_key}="${val}" `;
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefault(a, b) {
|
export function getDefault(a, b) {
|
||||||
return a !== undefined ? a : b;
|
return a !== undefined ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function escapeRegExp(str) {
|
export function escapeRegExp(str) {
|
||||||
// https://stackoverflow.com/a/6969486
|
// https://stackoverflow.com/a/6969486
|
||||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCSSString(styleMap) {
|
export function getCSSString(styleMap) {
|
||||||
let style = '';
|
let style = '';
|
||||||
|
|
||||||
for (const prop in styleMap) {
|
for (const prop in styleMap) {
|
||||||
if (styleMap.hasOwnProperty(prop)) {
|
if (styleMap.hasOwnProperty(prop)) {
|
||||||
style += `${prop}: ${styleMap[prop]}; `;
|
style += `${prop}: ${styleMap[prop]}; `;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return style.trim();
|
return style.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCSSRuleBlock(rule, styleMap) {
|
export function getCSSRuleBlock(rule, styleMap) {
|
||||||
return `${rule} { ${getCSSString(styleMap)} }`;
|
return `${rule} { ${getCSSString(styleMap)} }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildCSSRule(rule, styleMap, cssRulesString = '') {
|
export function buildCSSRule(rule, styleMap, cssRulesString = '') {
|
||||||
// build css rules efficiently,
|
// build css rules efficiently,
|
||||||
// append new rule if doesnt exist,
|
// append new rule if doesnt exist,
|
||||||
// update existing ones
|
// update existing ones
|
||||||
|
|
||||||
const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
|
const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
|
||||||
const rulePattern = new RegExp(rulePatternStr, 'g');
|
const rulePattern = new RegExp(rulePatternStr, 'g');
|
||||||
|
|
||||||
if (cssRulesString && cssRulesString.match(rulePattern)) {
|
if (cssRulesString && cssRulesString.match(rulePattern)) {
|
||||||
for (const property in styleMap) {
|
for (const property in styleMap) {
|
||||||
const value = styleMap[property];
|
const value = styleMap[property];
|
||||||
const propPattern = new RegExp(`${escapeRegExp(property)}:([^;]*);`);
|
const propPattern = new RegExp(`${escapeRegExp(property)}:([^;]*);`);
|
||||||
|
|
||||||
cssRulesString = cssRulesString.replace(rulePattern, function (match, propertyStr) {
|
cssRulesString = cssRulesString.replace(rulePattern, function (match, propertyStr) {
|
||||||
if (propertyStr.match(propPattern)) {
|
if (propertyStr.match(propPattern)) {
|
||||||
// property exists, replace value with new value
|
// property exists, replace value with new value
|
||||||
propertyStr = propertyStr.replace(propPattern, (match, valueStr) => {
|
propertyStr = propertyStr.replace(propPattern, (match, valueStr) => {
|
||||||
return `${property}: ${value};`;
|
return `${property}: ${value};`;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
propertyStr = propertyStr.trim();
|
||||||
|
|
||||||
|
const replacer =
|
||||||
|
`${rule} { ${propertyStr} }`;
|
||||||
|
|
||||||
|
return replacer;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
propertyStr = propertyStr.trim();
|
|
||||||
|
|
||||||
const replacer =
|
return cssRulesString;
|
||||||
`${rule} { ${propertyStr} }`;
|
|
||||||
|
|
||||||
return replacer;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// no match, append new rule block
|
||||||
return cssRulesString;
|
return `${cssRulesString}${getCSSRuleBlock(rule, styleMap)}`;
|
||||||
}
|
|
||||||
// no match, append new rule block
|
|
||||||
return `${cssRulesString}${getCSSRuleBlock(rule, styleMap)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeCSSRule(rule, cssRulesString = '') {
|
export function removeCSSRule(rule, cssRulesString = '') {
|
||||||
const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
|
const rulePatternStr = `${escapeRegExp(rule)} {([^}]*)}`;
|
||||||
const rulePattern = new RegExp(rulePatternStr, 'g');
|
const rulePattern = new RegExp(rulePatternStr, 'g');
|
||||||
let output = cssRulesString;
|
let output = cssRulesString;
|
||||||
|
|
||||||
if (cssRulesString && cssRulesString.match(rulePattern)) {
|
if (cssRulesString && cssRulesString.match(rulePattern)) {
|
||||||
output = cssRulesString.replace(rulePattern, '');
|
output = cssRulesString.replace(rulePattern, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.trim();
|
return output.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copyTextToClipboard(text) {
|
export function copyTextToClipboard(text) {
|
||||||
// https://stackoverflow.com/a/30810322/5353542
|
// https://stackoverflow.com/a/30810322/5353542
|
||||||
var textArea = document.createElement('textarea');
|
var textArea = document.createElement('textarea');
|
||||||
|
|
||||||
//
|
//
|
||||||
// *** This styling is an extra step which is likely not required. ***
|
// *** This styling is an extra step which is likely not required. ***
|
||||||
//
|
//
|
||||||
// Why is it here? To ensure:
|
// Why is it here? To ensure:
|
||||||
// 1. the element is able to have focus and selection.
|
// 1. the element is able to have focus and selection.
|
||||||
// 2. if element was to flash render it has minimal visual impact.
|
// 2. if element was to flash render it has minimal visual impact.
|
||||||
// 3. less flakyness with selection and copying which **might** occur if
|
// 3. less flakyness with selection and copying which **might** occur if
|
||||||
// the textarea element is not visible.
|
// the textarea element is not visible.
|
||||||
//
|
//
|
||||||
// The likelihood is the element won't even render, not even a flash,
|
// 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
|
// so some of these are just precautions. However in IE the element
|
||||||
// is visible whilst the popup box asking the user for permission for
|
// is visible whilst the popup box asking the user for permission for
|
||||||
// the web page to copy to the clipboard.
|
// the web page to copy to the clipboard.
|
||||||
//
|
//
|
||||||
|
|
||||||
// Place in top-left corner of screen regardless of scroll position.
|
// Place in top-left corner of screen regardless of scroll position.
|
||||||
textArea.style.position = 'fixed';
|
textArea.style.position = 'fixed';
|
||||||
textArea.style.top = 0;
|
textArea.style.top = 0;
|
||||||
textArea.style.left = 0;
|
textArea.style.left = 0;
|
||||||
|
|
||||||
// Ensure it has a small width and height. Setting to 1px / 1em
|
// 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.
|
// doesn't work as this gives a negative w/h on some browsers.
|
||||||
textArea.style.width = '2em';
|
textArea.style.width = '2em';
|
||||||
textArea.style.height = '2em';
|
textArea.style.height = '2em';
|
||||||
|
|
||||||
// We don't need padding, reducing the size if it does flash render.
|
// We don't need padding, reducing the size if it does flash render.
|
||||||
textArea.style.padding = 0;
|
textArea.style.padding = 0;
|
||||||
|
|
||||||
// Clean up any borders.
|
// Clean up any borders.
|
||||||
textArea.style.border = 'none';
|
textArea.style.border = 'none';
|
||||||
textArea.style.outline = 'none';
|
textArea.style.outline = 'none';
|
||||||
textArea.style.boxShadow = 'none';
|
textArea.style.boxShadow = 'none';
|
||||||
|
|
||||||
// Avoid flash of white box if rendered for any reason.
|
// Avoid flash of white box if rendered for any reason.
|
||||||
textArea.style.background = 'transparent';
|
textArea.style.background = 'transparent';
|
||||||
|
|
||||||
textArea.value = text;
|
textArea.value = text;
|
||||||
|
|
||||||
document.body.appendChild(textArea);
|
document.body.appendChild(textArea);
|
||||||
|
|
||||||
textArea.select();
|
textArea.select();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Oops, unable to copy');
|
console.log('Oops, unable to copy');
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.removeChild(textArea);
|
document.body.removeChild(textArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNumeric(val) {
|
export function isNumeric(val) {
|
||||||
return !isNaN(val);
|
return !isNaN(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
export let throttle = _throttle;
|
export let throttle = _throttle;
|
||||||
@ -156,30 +156,30 @@ export let throttle = _throttle;
|
|||||||
export let debounce = _debounce;
|
export let debounce = _debounce;
|
||||||
|
|
||||||
export function promisify(fn, context = null) {
|
export function promisify(fn, context = null) {
|
||||||
return (...args) => {
|
return (...args) => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const out = fn.apply(context, args);
|
const out = fn.apply(context, args);
|
||||||
resolve(out);
|
resolve(out);
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function chainPromises(promises) {
|
export function chainPromises(promises) {
|
||||||
return promises.reduce(
|
return promises.reduce(
|
||||||
(prev, cur) => prev.then(cur), Promise.resolve()
|
(prev, cur) => prev.then(cur), Promise.resolve()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function linkProperties(target, source, properties) {
|
export function linkProperties(target, source, properties) {
|
||||||
const props = properties.reduce((acc, prop) => {
|
const props = properties.reduce((acc, prop) => {
|
||||||
acc[prop] = {
|
acc[prop] = {
|
||||||
get() {
|
get() {
|
||||||
return source[prop];
|
return source[prop];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
Object.defineProperties(target, props);
|
Object.defineProperties(target, props);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user