UNPKG

frappe-datatable

Version:

A modern datatable library for the web

370 lines (307 loc) 10.9 kB
import $ from './dom'; import { makeDataAttributeString, nextTick, ensureArray, linkProperties, uniq, numberSortAsc } from './utils'; export default class RowManager { constructor(instance) { this.instance = instance; linkProperties(this, this.instance, [ 'options', 'fireEvent', 'wrapper', 'bodyScrollable', 'bodyRenderer', 'style' ]); this.bindEvents(); this.refreshRows = nextTick(this.refreshRows, this); } get datamanager() { return this.instance.datamanager; } get cellmanager() { return this.instance.cellmanager; } bindEvents() { this.bindCheckbox(); } bindCheckbox() { if (!this.options.checkboxColumn) return; // map of checked rows this.checkMap = []; $.on(this.wrapper, 'click', '.dt-cell--col-0 [type="checkbox"]', (e, $checkbox) => { const $cell = $checkbox.closest('.dt-cell'); 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, true); }); } getCheckedRows() { if (!this.checkMap) { return []; } let out = []; for (let rowIndex in this.checkMap) { const checked = this.checkMap[rowIndex]; if (checked === 1) { out.push(rowIndex); } } return out; } highlightCheckedRows() { this.getCheckedRows() .map(rowIndex => this.checkRow(rowIndex, true)); } checkRow(rowIndex, toggle) { const value = toggle ? 1 : 0; const selector = rowIndex => `.dt-cell--0-${rowIndex} [type="checkbox"]`; // update internal map this.checkMap[rowIndex] = value; // set checkbox value explicitly $.each(selector(rowIndex), this.bodyScrollable) .map(input => { input.checked = toggle; }); // highlight row this.highlightRow(rowIndex, toggle); this.showCheckStatus(); this.fireEvent('onCheckRow', this.datamanager.getRow(rowIndex)); } checkAll(toggle) { const value = toggle ? 1 : 0; // update internal map if (toggle) { if (this.datamanager._filteredRows) { this.datamanager._filteredRows.forEach(f => { this.checkRow(f, toggle); }); } else { this.checkMap = Array.from(Array(this.getTotalRows())).map(c => value); } } else { this.checkMap = []; } // set checkbox value $.each('.dt-cell--col-0 [type="checkbox"]', this.bodyScrollable) .map(input => { input.checked = toggle; }); // highlight all this.highlightAll(toggle); this.showCheckStatus(); this.fireEvent('onCheckRow'); } showCheckStatus() { if (!this.options.checkedRowStatus) return; const checkedRows = this.getCheckedRows(); const count = checkedRows.length; if (count > 0) { let message = this.instance.translate('{count} rows selected', { count: count }); this.bodyRenderer.showToastMessage(message); } else { this.bodyRenderer.clearToastMessage(); } } highlightRow(rowIndex, toggle = true) { const $row = this.getRow$(rowIndex); if (!$row) return; if (!toggle && this.bodyScrollable.classList.contains('dt-scrollable--highlight-all')) { $row.classList.add('dt-row--unhighlight'); return; } if (toggle && $row.classList.contains('dt-row--unhighlight')) { $row.classList.remove('dt-row--unhighlight'); } this._highlightedRows = this._highlightedRows || {}; if (toggle) { $row.classList.add('dt-row--highlight'); this._highlightedRows[rowIndex] = $row; } else { $row.classList.remove('dt-row--highlight'); delete this._highlightedRows[rowIndex]; } } highlightAll(toggle = true) { if (toggle) { this.bodyScrollable.classList.add('dt-scrollable--highlight-all'); } else { this.bodyScrollable.classList.remove('dt-scrollable--highlight-all'); for (const rowIndex in this._highlightedRows) { const $row = this._highlightedRows[rowIndex]; $row.classList.remove('dt-row--highlight'); } this._highlightedRows = {}; } } showRows(rowIndices) { rowIndices = ensureArray(rowIndices); const rows = rowIndices.map(rowIndex => this.datamanager.getRow(rowIndex)); this.bodyRenderer.renderRows(rows); } showAllRows() { const rowIndices = this.datamanager.getAllRowIndices(); this.showRows(rowIndices); } getChildrenToShowForNode(rowIndex) { const row = this.datamanager.getRow(rowIndex); row.meta.isTreeNodeClose = false; return this.datamanager.getImmediateChildren(rowIndex); } openSingleNode(rowIndex) { const childrenToShow = this.getChildrenToShowForNode(rowIndex); const visibleRowIndices = this.bodyRenderer.visibleRowIndices; const rowsToShow = uniq([...childrenToShow, ...visibleRowIndices]).sort(numberSortAsc); this.showRows(rowsToShow); } getChildrenToHideForNode(rowIndex) { const row = this.datamanager.getRow(rowIndex); row.meta.isTreeNodeClose = true; const rowsToHide = this.datamanager.getChildren(rowIndex); rowsToHide.forEach(rowIndex => { const row = this.datamanager.getRow(rowIndex); if (!row.meta.isLeaf) { row.meta.isTreeNodeClose = true; } }); return rowsToHide; } closeSingleNode(rowIndex) { const rowsToHide = this.getChildrenToHideForNode(rowIndex); const visibleRows = this.bodyRenderer.visibleRowIndices; const rowsToShow = visibleRows .filter(rowIndex => !rowsToHide.includes(rowIndex)) .sort(numberSortAsc); this.showRows(rowsToShow); } expandAllNodes() { let rows = this.datamanager.getRows(); let rootNodes = rows.filter(row => !row.meta.isLeaf); const childrenToShow = rootNodes.map(row => this.getChildrenToShowForNode(row.meta.rowIndex)).flat(); const visibleRowIndices = this.bodyRenderer.visibleRowIndices; const rowsToShow = uniq([...childrenToShow, ...visibleRowIndices]).sort(numberSortAsc); this.showRows(rowsToShow); } collapseAllNodes() { let rows = this.datamanager.getRows(); let rootNodes = rows.filter(row => row.meta.indent === 0); const rowsToHide = rootNodes.map(row => this.getChildrenToHideForNode(row.meta.rowIndex)).flat(); const visibleRows = this.bodyRenderer.visibleRowIndices; const rowsToShow = visibleRows .filter(rowIndex => !rowsToHide.includes(rowIndex)) .sort(numberSortAsc); this.showRows(rowsToShow); } setTreeDepth(depth) { let rows = this.datamanager.getRows(); const rowsToOpen = rows.filter(row => row.meta.indent < depth); const rowsToClose = rows.filter(row => row.meta.indent >= depth); const rowsToHide = rowsToClose.filter(row => row.meta.indent > depth); rowsToClose.forEach(row => { if (!row.meta.isLeaf) { row.meta.isTreeNodeClose = true; } }); rowsToOpen.forEach(row => { if (!row.meta.isLeaf) { row.meta.isTreeNodeClose = false; } }); const rowsToShow = rows .filter(row => !rowsToHide.includes(row)) .map(row => row.meta.rowIndex) .sort(numberSortAsc); this.showRows(rowsToShow); } getRow$(rowIndex) { return $(this.selector(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); let rowIdentifier = props.rowIndex; if (props.isFilter) { row = row.map(cell => (Object.assign({}, cell, { content: this.getFilterInput({ colIndex: cell.colIndex, name: cell.name }), isFilter: 1, isHeader: undefined, editable: false }))); rowIdentifier = 'filter'; } if (props.isHeader) { rowIdentifier = 'header'; } return ` <div class="dt-row dt-row-${rowIdentifier}" ${dataAttr}> ${row.map(cell => this.cellmanager.getCellHTML(cell)).join('')} </div> `; } getFilterInput(props) { let title = `title="Filter based on ${props.name || 'Index'}"`; const dataAttr = makeDataAttributeString(props); return `<input class="dt-filter dt-input" type="text" ${dataAttr} tabindex="1" ${props.colIndex === 0 ? 'disabled' : title} />`; } selector(rowIndex) { return `.dt-row-${rowIndex}`; } }