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 = ''; 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 `