datatable/src/columnmanager.js
2017-11-29 13:31:15 +05:30

480 lines
13 KiB
JavaScript

import $ from './dom';
import Sortable from 'sortablejs';
import { getRowHTML } from './rowmanager';
import { getDefault, throttle } from './utils';
export default class ColumnManager {
constructor(instance) {
this.instance = instance;
this.options = this.instance.options;
this.fireEvent = this.instance.fireEvent;
this.header = this.instance.header;
this.datamanager = this.instance.datamanager;
this.style = this.instance.style;
this.wrapper = this.instance.wrapper;
this.rowmanager = this.instance.rowmanager;
this.bindEvents();
getDropdownHTML = getDropdownHTML.bind(this, this.options.dropdownButton);
}
renderHeader() {
this.header.innerHTML = '<thead></thead>';
this.refreshHeader();
}
refreshHeader() {
const columns = this.datamanager.getColumns();
if (!$('.data-table-col', this.header)) {
// insert html
$('thead', this.header).innerHTML = getRowHTML(columns, { isHeader: 1 });
} else {
// refresh dom state
const $cols = $.each('.data-table-col', this.header);
if (columns.length < $cols.length) {
// deleted column
$('thead', this.header).innerHTML = getRowHTML(columns, { isHeader: 1 });
return;
}
// column sorted or order changed
// update colIndex of each header cell
$cols.map(($col, i) => {
$.data($col, {
colIndex: columns[i].colIndex
});
});
// show sort indicator
const sortColIndex = this.datamanager.currentSort.colIndex;
if (sortColIndex !== -1) {
const order = this.datamanager.currentSort.sortOrder;
$('.sort-indicator', $cols[sortColIndex]).innerHTML = this.options.sortIndicator[order];
}
}
// reset columnMap
this.$columnMap = [];
}
bindEvents() {
this.bindDropdown();
this.bindResizeColumn();
this.bindMoveColumn();
}
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() {
let isDragging = false;
let $resizingCell, startWidth, startX;
$.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => {
const $cell = $handle.parentNode.parentNode;
$resizingCell = $cell;
const { colIndex } = $.data($resizingCell);
const col = this.getColumn(colIndex);
if (col && col.resizable === false) {
return;
}
isDragging = true;
startWidth = $.style($('.content', $resizingCell), 'width');
startX = e.pageX;
});
$.on(document.body, 'mouseup', (e) => {
if (!$resizingCell) return;
isDragging = false;
const { colIndex } = $.data($resizingCell);
const width = $.style($('.content', $resizingCell), 'width');
this.setColumnWidth(colIndex, width);
this.instance.setBodyWidth();
$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.setColumnHeaderWidth(colIndex, finalWidth);
});
}
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);
$.on(document, 'drag', '.data-table-col', throttle((e, $target) => {
if (e.offsetY > 200) {
$target.classList.add('remove-column');
} else {
setTimeout(() => {
$target.classList.remove('remove-column');
}, 10);
}
}));
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;
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)
);
});
}
setDimensions() {
this.setHeaderStyle();
this.setupMinWidth();
this.setNaturalColumnWidth();
this.distributeRemainingWidth();
this.setColumnAlignments();
}
setHeaderStyle() {
if (!this.options.takeAvailableSpace) {
// setting width as 0 will ensure that the
// header doesn't take the available space
$.style(this.header, {
width: 0
});
}
$.style(this.header, {
margin: 0
});
// don't show resize cursor on nonResizable columns
const nonResizableColumnsSelector = this.datamanager.getColumns()
.filter(col => col.resizable !== undefined && !col.resizable)
.map(col => col.colIndex)
.map(i => `.data-table-header [data-col-index="${i}"]`)
.join();
this.style.setStyle(nonResizableColumnsSelector, {
cursor: 'pointer'
});
}
setupMinWidth() {
// cache minWidth for each column
this.minWidthMap = getDefault(this.minWidthMap, []);
$.each('.data-table-col', this.header).map(col => {
const width = $.style($('.content', col), 'width');
const { colIndex } = $.data(col);
if (!this.minWidthMap[colIndex]) {
// only set this once
this.minWidthMap[colIndex] = width;
}
});
}
setNaturalColumnWidth() {
// set initial width as naturally calculated by table's first row
$.each('.data-table-row[data-row-index="0"] .data-table-col', this.bodyScrollable).map($cell => {
let width = $.style($('.content', $cell), 'width');
const height = $.style($('.content', $cell), 'height');
const { colIndex } = $.data($cell);
const minWidth = this.getColumnMinWidth(colIndex);
if (width < minWidth) {
width = minWidth;
}
this.setColumnWidth(colIndex, width);
this.setDefaultCellHeight(height);
});
}
distributeRemainingWidth() {
if (!this.options.takeAvailableSpace) return;
const wrapperWidth = $.style(this.instance.datatableWrapper, 'width');
const headerWidth = $.style(this.header, 'width');
if (headerWidth >= wrapperWidth) {
// don't resize, horizontal scroll takes place
return;
}
const resizableColumns = this.datamanager.getColumns().filter(
col => col.resizable === undefined || col.resizable
);
const deltaWidth = (wrapperWidth - headerWidth) / resizableColumns.length;
resizableColumns.map(col => {
const width = $.style(this.getColumnHeaderElement(col.colIndex), 'width');
let finalWidth = Math.min(width + deltaWidth) - 2;
this.setColumnHeaderWidth(col.colIndex, finalWidth);
this.setColumnWidth(col.colIndex, finalWidth);
});
this.instance.setBodyWidth();
}
setDefaultCellHeight(height) {
this.style.setStyle('.data-table-col .content', {
height: height + 'px'
});
}
setColumnAlignments() {
// align columns
this.getColumns()
.map(column => {
if (['left', 'center', 'right'].includes(column.align)) {
this.style.setStyle(`[data-col-index="${column.colIndex}"]`, {
'text-align': column.align
});
}
});
}
sortRows(colIndex, sortOrder) {
return this.datamanager.sortRows(colIndex, sortOrder);
}
getColumn(colIndex) {
return this.datamanager.getColumn(colIndex);
}
getColumns() {
return this.datamanager.getColumns();
}
setColumnWidth(colIndex, width) {
this._columnWidthMap = this._columnWidthMap || [];
if (!width) {
const $headerContent = $(`.data-table-col[data-col-index="${colIndex}"] .content`, this.header);
width = $.style($headerContent, 'width');
}
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, width) {
this.$columnMap = this.$columnMap || [];
const selector = `[data-col-index="${colIndex}"][data-is-header] .content`;
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.minWidthMap && this.minWidthMap[colIndex];
}
getFirstColumnIndex() {
if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) {
return 2;
}
if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) {
return 1;
}
return 0;
}
getHeaderCell$(colIndex) {
return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header);
}
getLastColumnIndex() {
return this.datamanager.getColumnCount() - 1;
}
getColumnHeaderElement(colIndex) {
colIndex = +colIndex;
if (colIndex < 0) return null;
return $(`.data-table-col[data-is-header][data-col-index="${colIndex}"]`, this.wrapper);
}
getSerialColumnIndex() {
const columns = this.datamanager.getColumns();
return columns.findIndex(column => column.content.includes('Sr. No'));
}
}
// eslint-disable-next-line
var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') {
// add dropdown buttons
const dropdownItems = this.options.headerDropdown;
return `<div class="data-table-dropdown-toggle">${dropdownButton}</div>
<div class="data-table-dropdown-list">
${dropdownItems.map((d, i) => `<div data-index="${i}">${d.label}</div>`).join('')}
</div>
`;
};
export {
getDropdownHTML
};