diff --git a/dist/frappe-datatable.cjs.css b/dist/frappe-datatable.cjs.css index 30dd637..ef507fe 100644 --- a/dist/frappe-datatable.cjs.css +++ b/dist/frappe-datatable.cjs.css @@ -178,33 +178,33 @@ background-color: #f5f7fa; } -.data-table-header .data-table-col.remove-column { +.data-table-header .data-table-cell.remove-column { background-color: #FD8B8B; -webkit-transition: 300ms background-color ease-in-out; transition: 300ms background-color ease-in-out; } -.data-table-header .data-table-col.sortable-chosen { +.data-table-header .data-table-cell.sortable-chosen { background-color: #f5f7fa; } -.data-table-col { +.data-table-cell { position: relative; } -.data-table-col .content { +.data-table-cell .content { padding: 8px; padding: 0.5rem; border: 2px solid transparent; } -.data-table-col .content.ellipsis { +.data-table-cell .content.ellipsis { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } -.data-table-col .edit-cell { +.data-table-cell .edit-cell { display: none; padding: 8px; padding: 0.5rem; @@ -213,31 +213,51 @@ height: 100%; } -.data-table-col.selected .content { +.data-table-cell.selected .content { border: 2px solid rgb(82, 146, 247); } -.data-table-col.editing .content { +.data-table-cell.editing .content { display: none; } -.data-table-col.editing .edit-cell { +.data-table-cell.editing .edit-cell { border: 2px solid rgb(82, 146, 247); display: block; } -.data-table-col.highlight { +.data-table-cell.highlight { background-color: #f5f7fa; } -.data-table-col:hover .column-resizer { +.data-table-cell:hover .column-resizer { display: inline-block; } -.data-table-col:hover .data-table-dropdown-toggle { +.data-table-cell:hover .data-table-dropdown-toggle { display: block; } +.data-table-cell .tree-node { + display: inline-block; + position: relative; + } + +.data-table-cell .toggle { + display: inline-block; + position: absolute; + padding: 0 4px; + cursor: pointer; + } + +.data-table-cell .toggle:before { + content: '▼'; + } + +.data-table-cell.tree-close .toggle:before { + content: '►'; + } + .data-table-row.row-highlight { background-color: #f5f7fa; } diff --git a/dist/frappe-datatable.cjs.js b/dist/frappe-datatable.cjs.js index a808358..9447593 100644 --- a/dist/frappe-datatable.cjs.js +++ b/dist/frappe-datatable.cjs.js @@ -847,6 +847,7 @@ class DataManager { this.prepareColumns(); this.prepareRows(); + this.prepareRowView(); this.prepareNumericColumns(); } @@ -964,6 +965,9 @@ class DataManager { const index = this._getNextRowCount(); let row = []; + let meta = { + rowIndex: index + }; if (Array.isArray(d)) { // row is an array @@ -990,17 +994,25 @@ class DataManager { row.push(d[col.id]); } } + + meta.indent = d.indent; } - return this.prepareRow(row, { - rowIndex: index - }); + return this.prepareRow(row, meta); }); } - prepareRow(row, props) { + prepareRowView() { + // This is order in which rows will be rendered in the table. + // When sorting happens, only this.rowViewOrder will change + // and not the original this.rows + this.rowViewOrder = this.rows.map(row => row.meta.rowIndex); + } + + prepareRow(row, meta) { const baseRowCell = { - rowIndex: props.rowIndex + rowIndex: meta.rowIndex, + indent: meta.indent }; row = row @@ -1008,7 +1020,7 @@ class DataManager { .map(cell => Object.assign({}, baseRowCell, cell)); // monkey patched in array object - row.meta = props; + row.meta = meta; return row; } @@ -1063,28 +1075,28 @@ class DataManager { (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') || (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc') ) { - this.reverseArray(this.rows); + this.reverseArray(this.rowViewOrder); this.currentSort.sortOrder = sortOrder; return; } } - this.rows.sort((a, b) => { - const _aIndex = a[0].rowIndex; - const _bIndex = b[0].rowIndex; - const _a = a[colIndex].content; - const _b = b[colIndex].content; + this.rowViewOrder.sort((a, b) => { + const aIndex = a; + const bIndex = b; + const aContent = this.getCell(colIndex, a).content; + const bContent = this.getCell(colIndex, b).content; if (sortOrder === 'none') { - return _aIndex - _bIndex; + return aIndex - bIndex; } else if (sortOrder === 'asc') { - if (_a < _b) return -1; - if (_a > _b) return 1; - if (_a === _b) return 0; + if (aContent < bContent) return -1; + if (aContent > bContent) return 1; + if (aContent === bContent) return 0; } else if (sortOrder === 'desc') { - if (_a < _b) return 1; - if (_a > _b) return -1; - if (_a === _b) return 0; + if (aContent < bContent) return 1; + if (aContent > bContent) return -1; + if (aContent === bContent) return 0; } return 0; }); @@ -1093,11 +1105,9 @@ class DataManager { // update row index const srNoColIndex = this.getColumnIndexById('_rowIndex'); this.rows.forEach((row, index) => { - row.forEach(cell => { - if (cell.colIndex === srNoColIndex) { - cell.content = (index + 1) + ''; - } - }); + const viewIndex = this.rowViewOrder.indexOf(index); + const cell = row[srNoColIndex]; + cell.content = (viewIndex + 1) + ''; }); } } @@ -1260,6 +1270,11 @@ class DataManager { return this.rows.slice(start, end); } + getRowsForView(start, end) { + const rows = this.rowViewOrder.map(i => this.rows[i]); + return rows.slice(start, end); + } + getColumns(skipStandardColumns) { let columns = this.columns; @@ -1299,13 +1314,33 @@ class DataManager { getRow(rowIndex) { rowIndex = +rowIndex; - return this.rows.find(row => row[0].rowIndex === rowIndex); + return this.rows[rowIndex]; } getCell(colIndex, rowIndex) { rowIndex = +rowIndex; colIndex = +colIndex; - return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex]; + return this.getRow(rowIndex)[colIndex]; + } + + getChildrenIndices(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + + let i = parentRowIndex + 1; + let nextRow = this.getRow(i); + let nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + + while (nextIndent > parentIndent) { + out.push(i); + + i++; + nextRow = this.getRow(i); + nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + } + + return out; } get() { @@ -1365,11 +1400,11 @@ class ColumnManager { refreshHeader() { const columns = this.datamanager.getColumns(); - const $cols = $.each('.data-table-col[data-is-header]', this.header); + const $cols = $.each('.data-table-cell[data-is-header]', this.header); const refreshHTML = // first init - !$('.data-table-col', this.header) || + !$('.data-table-cell', this.header) || // deleted column columns.length < $cols.length; @@ -1378,7 +1413,9 @@ class ColumnManager { $('thead', this.header).innerHTML = this.getHeaderHTML(columns); this.$filterRow = $('.data-table-row[data-is-filter]', this.header); - $.style(this.$filterRow, { display: 'none' }); + if (this.$filterRow) { + $.style(this.$filterRow, { display: 'none' }); + } } else { // update data-attributes $cols.map(($col, i) => { @@ -1441,7 +1478,7 @@ class ColumnManager { const dropdownItems = this.options.headerDropdown; $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => { - const $col = $.closest('.data-table-col', $item); + const $col = $.closest('.data-table-cell', $item); const { index } = $.data($item); @@ -1463,7 +1500,7 @@ class ColumnManager { let isDragging = false; let $resizingCell, startWidth, startX; - $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => { + $.on(this.header, 'mousedown', '.data-table-cell .column-resizer', (e, $handle) => { document.body.classList.add('data-table-resize'); const $cell = $handle.parentNode.parentNode; $resizingCell = $cell; @@ -1520,7 +1557,7 @@ class ColumnManager { $.off(document.body, 'mousemove', initialize); return; } - const ready = $('.data-table-col', this.header); + const ready = $('.data-table-cell', this.header); if (!ready) return; const $parent = $('.data-table-row', this.header); @@ -1550,8 +1587,8 @@ class ColumnManager { bindSortColumn() { - $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => { - const $cell = span.closest('.data-table-col'); + $.on(this.header, 'click', '.data-table-cell .column-title', (e, span) => { + const $cell = span.closest('.data-table-cell'); let { colIndex, sortOrder @@ -1565,7 +1602,7 @@ class ColumnManager { // reset sort indicator $('.sort-indicator', this.header).textContent = ''; - $.each('.data-table-col', this.header).map($cell => { + $.each('.data-table-cell', this.header).map($cell => { $.data($cell, { sortOrder: 'none' }); @@ -1666,7 +1703,7 @@ class ColumnManager { bindFilter() { if (!this.options.enableInlineFilters) return; const handler = e => { - const $filterCell = $.closest('.data-table-col', e.target); + const $filterCell = $.closest('.data-table-cell', e.target); const { colIndex } = $.data($filterCell); @@ -1677,14 +1714,8 @@ class ColumnManager { 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'); - }); + this.rowmanager.hideRows(rowsToHide); + this.rowmanager.showRows(rowsToShow); }); }; $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300)); @@ -1755,7 +1786,7 @@ class ColumnManager { } getHeaderCell$(colIndex) { - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getLastColumnIndex() { @@ -1802,6 +1833,7 @@ class CellManager { this.bindKeyboardSelection(); this.bindCopyCellContents(); this.bindMouseEvents(); + this.bindTreeEvents(); } bindFocusCell() { @@ -1811,7 +1843,7 @@ class CellManager { bindEditCell() { this.$editingCell = null; - $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => { + $.on(this.bodyScrollable, 'dblclick', '.data-table-cell', (e, cell) => { this.activateEditing(cell); }); @@ -1888,7 +1920,7 @@ class CellManager { if (this.options.enableInlineFilters) { this.keyboard.on('ctrl+f', (e) => { - const $cell = $.closest('.data-table-col', e.target); + const $cell = $.closest('.data-table-cell', e.target); let { colIndex } = $.data($cell); @@ -1931,7 +1963,7 @@ class CellManager { bindMouseEvents() { let mouseDown = null; - $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => { + $.on(this.bodyScrollable, 'mousedown', '.data-table-cell', (e) => { mouseDown = true; this.focusCell($(e.delegatedTarget)); }); @@ -1945,7 +1977,39 @@ class CellManager { this.selectArea($(e.delegatedTarget)); }; - $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50)); + $.on(this.bodyScrollable, 'mousemove', '.data-table-cell', throttle$1(selectArea, 50)); + } + + bindTreeEvents() { + $.on(this.bodyScrollable, 'click', '.toggle', (e, $toggle) => { + const $cell = $.closest('.data-table-cell', $toggle); + const { rowIndex } = $.data($cell); + + if ($cell.classList.contains('tree-close')) { + this.rowmanager.openTreeNode(rowIndex); + $cell.classList.remove('tree-close'); + } else { + this.rowmanager.closeTreeNode(rowIndex); + $cell.classList.add('tree-close'); + } + }); + + // this.keyboard.on('left, right', (e) => { + // const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + // if (e.target.matches('.data-table-cell')) { + // const $cell = e.target; + // const { colIndex, rowIndex } = $.data($cell); + // if (+colIndex === firstColumnIndex) { + // if (keyCode[e.keyCode] === 'left') { + // this.rowmanager.closeTreeNode(rowIndex); + // } + // if (keyCode[e.keyCode] === 'right') { + // this.rowmanager.openTreeNode(rowIndex); + // } + // return false; + // } + // } + // }); } focusCell($cell, { @@ -1995,8 +2059,8 @@ class CellManager { rowIndex } = $.data($cell); const _colIndex = this.datamanager.getColumnIndexById('_rowIndex'); - const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`; - const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; + const colHeaderSelector = `.data-table-header .data-table-cell[data-col-index="${colIndex}"]`; + const rowHeaderSelector = `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; if (this.lastHeaders) { $.removeStyle(this.lastHeaders, 'backgroundColor'); @@ -2122,7 +2186,7 @@ class CellManager { } clearSelection() { - $.each('.data-table-col.highlight', this.bodyScrollable) + $.each('.data-table-cell.highlight', this.bodyScrollable) .map(cell => cell.classList.remove('highlight')); this.$selectionCursor = null; @@ -2394,15 +2458,16 @@ class CellManager { }); return ` - - ${this.getCellContent(cell)} - - `; + + ${this.getCellContent(cell)} + + `; } getCellContent(cell) { const { - isHeader + isHeader, + isFilter } = cell; const editable = !isHeader && cell.editable !== false; @@ -2418,21 +2483,35 @@ class CellManager { const dropdown = hasDropdown ? `
${getDropdownHTML()}
` : ''; let contentHTML; - if (cell.isHeader || cell.isFilter || !cell.column.format) { + if (isHeader || isFilter || !cell.column.format) { contentHTML = cell.content; } else { contentHTML = cell.column.format(cell.content, cell); } + if (!(isHeader || isFilter) && cell.indent !== undefined) { + const nextRow = this.datamanager.getRow(cell.rowIndex + 1); + const addToggle = nextRow && nextRow.meta.indent > cell.indent; + + // Add toggle and indent in the first column + const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + if (firstColumnIndex === cell.colIndex) { + const padding = ((cell.indent || 0) + 1) * 1.5; + const toggleHTML = addToggle ? `` : ''; + contentHTML = ` + ${toggleHTML}${contentHTML}`; + } + } + return ` -
- ${(contentHTML)} - ${sortIndicator} - ${resizeColumn} - ${dropdown} -
- ${editCellHTML} - `; +
+ ${contentHTML} + ${sortIndicator} + ${resizeColumn} + ${dropdown} +
+ ${editCellHTML} + `; } getEditCellHTML() { @@ -2442,7 +2521,7 @@ class CellManager { } cellSelector(colIndex, rowIndex) { - return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; + return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } @@ -2475,8 +2554,8 @@ class RowManager { // 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'); + $.on(this.wrapper, 'click', '.data-table-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { + const $cell = $checkbox.closest('.data-table-cell'); const { rowIndex, isHeader @@ -2529,7 +2608,7 @@ class RowManager { checkRow(rowIndex, toggle) { const value = toggle ? 1 : 0; const selector = rowIndex => - `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; + `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; // update internal map this.checkMap[rowIndex] = value; // set checkbox value explicitly @@ -2551,7 +2630,7 @@ class RowManager { this.checkMap = []; } // set checkbox value - $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) + $.each('.data-table-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) .map(input => { input.checked = toggle; }); @@ -2596,8 +2675,32 @@ class RowManager { } } + hideRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.add('hide'); + }); + } + + showRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.remove('hide'); + }); + } + + openTreeNode(rowIndex) { + const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + this.showRows(rowsToShow); + } + + closeTreeNode(rowIndex) { + const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); + this.hideRows(rowsToHide); + } + getRow$(rowIndex) { - return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable); + return $(this.selector(rowIndex), this.bodyScrollable); } getTotalRows() { @@ -2663,6 +2766,10 @@ class RowManager { const dataAttr = makeDataAttributeString(props); return ``; } + + selector(rowIndex) { + return `.data-table-row[data-row-index="${rowIndex}"]`; + } } class BodyRenderer { @@ -2686,7 +2793,7 @@ class BodyRenderer { } renderBodyHTML() { - const rows = this.datamanager.getRows(); + const rows = this.datamanager.getRowsForView(); this.bodyScrollable.innerHTML = ` @@ -2699,7 +2806,7 @@ class BodyRenderer { renderBodyWithClusterize() { // first page - const rows = this.datamanager.getRows(0, 20); + const rows = this.datamanager.getRowsForView(0, 20); const initialData = this.getDataForClusterize(rows); if (!this.clusterize) { @@ -2743,7 +2850,7 @@ class BodyRenderer { } appendRemainingData() { - const rows = this.datamanager.getRows(20); + const rows = this.datamanager.getRowsForView(20); const data = this.getDataForClusterize(rows); this.clusterize.append(data); } @@ -2860,7 +2967,7 @@ class Style { } setupMinWidth() { - $.each('.data-table-col[data-is-header]', this.header).map(col => { + $.each('.data-table-cell[data-is-header]', this.header).map(col => { const width = $.style($('.content', col), 'width'); const { colIndex @@ -2878,7 +2985,7 @@ class Style { if (!$('.data-table-row')) return; // 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 => { + $.each('.data-table-row[data-row-index="0"] .data-table-cell', this.bodyScrollable).map($cell => { const { colIndex } = $.data($cell); @@ -2927,7 +3034,7 @@ class Style { setDefaultCellHeight() { if (this.__cellHeightSet) return; const height = this.options.cellHeight || - $.style($('.data-table-col', this.instance.datatableWrapper), 'height'); + $.style($('.data-table-cell', this.instance.datatableWrapper), 'height'); if (height) { this.setCellHeight(height); this.__cellHeightSet = true; @@ -2935,10 +3042,10 @@ class Style { } setCellHeight(height) { - this.setStyle('.data-table-col .content', { + this.setStyle('.data-table-cell .content', { height: height + 'px' }); - this.setStyle('.data-table-col .edit-cell', { + this.setStyle('.data-table-cell .edit-cell', { height: height + 'px' }); } @@ -2987,7 +3094,7 @@ class Style { getColumnHeaderElement(colIndex) { colIndex = +colIndex; if (colIndex < 0) return null; - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getRowIndexColumnWidth(baseWidth) { diff --git a/dist/frappe-datatable.css b/dist/frappe-datatable.css index 30dd637..ef507fe 100644 --- a/dist/frappe-datatable.css +++ b/dist/frappe-datatable.css @@ -178,33 +178,33 @@ background-color: #f5f7fa; } -.data-table-header .data-table-col.remove-column { +.data-table-header .data-table-cell.remove-column { background-color: #FD8B8B; -webkit-transition: 300ms background-color ease-in-out; transition: 300ms background-color ease-in-out; } -.data-table-header .data-table-col.sortable-chosen { +.data-table-header .data-table-cell.sortable-chosen { background-color: #f5f7fa; } -.data-table-col { +.data-table-cell { position: relative; } -.data-table-col .content { +.data-table-cell .content { padding: 8px; padding: 0.5rem; border: 2px solid transparent; } -.data-table-col .content.ellipsis { +.data-table-cell .content.ellipsis { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } -.data-table-col .edit-cell { +.data-table-cell .edit-cell { display: none; padding: 8px; padding: 0.5rem; @@ -213,31 +213,51 @@ height: 100%; } -.data-table-col.selected .content { +.data-table-cell.selected .content { border: 2px solid rgb(82, 146, 247); } -.data-table-col.editing .content { +.data-table-cell.editing .content { display: none; } -.data-table-col.editing .edit-cell { +.data-table-cell.editing .edit-cell { border: 2px solid rgb(82, 146, 247); display: block; } -.data-table-col.highlight { +.data-table-cell.highlight { background-color: #f5f7fa; } -.data-table-col:hover .column-resizer { +.data-table-cell:hover .column-resizer { display: inline-block; } -.data-table-col:hover .data-table-dropdown-toggle { +.data-table-cell:hover .data-table-dropdown-toggle { display: block; } +.data-table-cell .tree-node { + display: inline-block; + position: relative; + } + +.data-table-cell .toggle { + display: inline-block; + position: absolute; + padding: 0 4px; + cursor: pointer; + } + +.data-table-cell .toggle:before { + content: '▼'; + } + +.data-table-cell.tree-close .toggle:before { + content: '►'; + } + .data-table-row.row-highlight { background-color: #f5f7fa; } diff --git a/dist/frappe-datatable.js b/dist/frappe-datatable.js index a5f1709..5f02555 100644 --- a/dist/frappe-datatable.js +++ b/dist/frappe-datatable.js @@ -846,6 +846,7 @@ class DataManager { this.prepareColumns(); this.prepareRows(); + this.prepareRowView(); this.prepareNumericColumns(); } @@ -963,6 +964,9 @@ class DataManager { const index = this._getNextRowCount(); let row = []; + let meta = { + rowIndex: index + }; if (Array.isArray(d)) { // row is an array @@ -989,17 +993,25 @@ class DataManager { row.push(d[col.id]); } } + + meta.indent = d.indent; } - return this.prepareRow(row, { - rowIndex: index - }); + return this.prepareRow(row, meta); }); } - prepareRow(row, props) { + prepareRowView() { + // This is order in which rows will be rendered in the table. + // When sorting happens, only this.rowViewOrder will change + // and not the original this.rows + this.rowViewOrder = this.rows.map(row => row.meta.rowIndex); + } + + prepareRow(row, meta) { const baseRowCell = { - rowIndex: props.rowIndex + rowIndex: meta.rowIndex, + indent: meta.indent }; row = row @@ -1007,7 +1019,7 @@ class DataManager { .map(cell => Object.assign({}, baseRowCell, cell)); // monkey patched in array object - row.meta = props; + row.meta = meta; return row; } @@ -1062,28 +1074,28 @@ class DataManager { (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') || (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc') ) { - this.reverseArray(this.rows); + this.reverseArray(this.rowViewOrder); this.currentSort.sortOrder = sortOrder; return; } } - this.rows.sort((a, b) => { - const _aIndex = a[0].rowIndex; - const _bIndex = b[0].rowIndex; - const _a = a[colIndex].content; - const _b = b[colIndex].content; + this.rowViewOrder.sort((a, b) => { + const aIndex = a; + const bIndex = b; + const aContent = this.getCell(colIndex, a).content; + const bContent = this.getCell(colIndex, b).content; if (sortOrder === 'none') { - return _aIndex - _bIndex; + return aIndex - bIndex; } else if (sortOrder === 'asc') { - if (_a < _b) return -1; - if (_a > _b) return 1; - if (_a === _b) return 0; + if (aContent < bContent) return -1; + if (aContent > bContent) return 1; + if (aContent === bContent) return 0; } else if (sortOrder === 'desc') { - if (_a < _b) return 1; - if (_a > _b) return -1; - if (_a === _b) return 0; + if (aContent < bContent) return 1; + if (aContent > bContent) return -1; + if (aContent === bContent) return 0; } return 0; }); @@ -1092,11 +1104,9 @@ class DataManager { // update row index const srNoColIndex = this.getColumnIndexById('_rowIndex'); this.rows.forEach((row, index) => { - row.forEach(cell => { - if (cell.colIndex === srNoColIndex) { - cell.content = (index + 1) + ''; - } - }); + const viewIndex = this.rowViewOrder.indexOf(index); + const cell = row[srNoColIndex]; + cell.content = (viewIndex + 1) + ''; }); } } @@ -1259,6 +1269,11 @@ class DataManager { return this.rows.slice(start, end); } + getRowsForView(start, end) { + const rows = this.rowViewOrder.map(i => this.rows[i]); + return rows.slice(start, end); + } + getColumns(skipStandardColumns) { let columns = this.columns; @@ -1298,13 +1313,33 @@ class DataManager { getRow(rowIndex) { rowIndex = +rowIndex; - return this.rows.find(row => row[0].rowIndex === rowIndex); + return this.rows[rowIndex]; } getCell(colIndex, rowIndex) { rowIndex = +rowIndex; colIndex = +colIndex; - return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex]; + return this.getRow(rowIndex)[colIndex]; + } + + getChildrenIndices(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + + let i = parentRowIndex + 1; + let nextRow = this.getRow(i); + let nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + + while (nextIndent > parentIndent) { + out.push(i); + + i++; + nextRow = this.getRow(i); + nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + } + + return out; } get() { @@ -1364,11 +1399,11 @@ class ColumnManager { refreshHeader() { const columns = this.datamanager.getColumns(); - const $cols = $.each('.data-table-col[data-is-header]', this.header); + const $cols = $.each('.data-table-cell[data-is-header]', this.header); const refreshHTML = // first init - !$('.data-table-col', this.header) || + !$('.data-table-cell', this.header) || // deleted column columns.length < $cols.length; @@ -1377,7 +1412,9 @@ class ColumnManager { $('thead', this.header).innerHTML = this.getHeaderHTML(columns); this.$filterRow = $('.data-table-row[data-is-filter]', this.header); - $.style(this.$filterRow, { display: 'none' }); + if (this.$filterRow) { + $.style(this.$filterRow, { display: 'none' }); + } } else { // update data-attributes $cols.map(($col, i) => { @@ -1440,7 +1477,7 @@ class ColumnManager { const dropdownItems = this.options.headerDropdown; $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => { - const $col = $.closest('.data-table-col', $item); + const $col = $.closest('.data-table-cell', $item); const { index } = $.data($item); @@ -1462,7 +1499,7 @@ class ColumnManager { let isDragging = false; let $resizingCell, startWidth, startX; - $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => { + $.on(this.header, 'mousedown', '.data-table-cell .column-resizer', (e, $handle) => { document.body.classList.add('data-table-resize'); const $cell = $handle.parentNode.parentNode; $resizingCell = $cell; @@ -1519,7 +1556,7 @@ class ColumnManager { $.off(document.body, 'mousemove', initialize); return; } - const ready = $('.data-table-col', this.header); + const ready = $('.data-table-cell', this.header); if (!ready) return; const $parent = $('.data-table-row', this.header); @@ -1549,8 +1586,8 @@ class ColumnManager { bindSortColumn() { - $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => { - const $cell = span.closest('.data-table-col'); + $.on(this.header, 'click', '.data-table-cell .column-title', (e, span) => { + const $cell = span.closest('.data-table-cell'); let { colIndex, sortOrder @@ -1564,7 +1601,7 @@ class ColumnManager { // reset sort indicator $('.sort-indicator', this.header).textContent = ''; - $.each('.data-table-col', this.header).map($cell => { + $.each('.data-table-cell', this.header).map($cell => { $.data($cell, { sortOrder: 'none' }); @@ -1665,7 +1702,7 @@ class ColumnManager { bindFilter() { if (!this.options.enableInlineFilters) return; const handler = e => { - const $filterCell = $.closest('.data-table-col', e.target); + const $filterCell = $.closest('.data-table-cell', e.target); const { colIndex } = $.data($filterCell); @@ -1676,14 +1713,8 @@ class ColumnManager { 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'); - }); + this.rowmanager.hideRows(rowsToHide); + this.rowmanager.showRows(rowsToShow); }); }; $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300)); @@ -1754,7 +1785,7 @@ class ColumnManager { } getHeaderCell$(colIndex) { - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getLastColumnIndex() { @@ -1801,6 +1832,7 @@ class CellManager { this.bindKeyboardSelection(); this.bindCopyCellContents(); this.bindMouseEvents(); + this.bindTreeEvents(); } bindFocusCell() { @@ -1810,7 +1842,7 @@ class CellManager { bindEditCell() { this.$editingCell = null; - $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => { + $.on(this.bodyScrollable, 'dblclick', '.data-table-cell', (e, cell) => { this.activateEditing(cell); }); @@ -1887,7 +1919,7 @@ class CellManager { if (this.options.enableInlineFilters) { this.keyboard.on('ctrl+f', (e) => { - const $cell = $.closest('.data-table-col', e.target); + const $cell = $.closest('.data-table-cell', e.target); let { colIndex } = $.data($cell); @@ -1930,7 +1962,7 @@ class CellManager { bindMouseEvents() { let mouseDown = null; - $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => { + $.on(this.bodyScrollable, 'mousedown', '.data-table-cell', (e) => { mouseDown = true; this.focusCell($(e.delegatedTarget)); }); @@ -1944,7 +1976,39 @@ class CellManager { this.selectArea($(e.delegatedTarget)); }; - $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50)); + $.on(this.bodyScrollable, 'mousemove', '.data-table-cell', throttle$1(selectArea, 50)); + } + + bindTreeEvents() { + $.on(this.bodyScrollable, 'click', '.toggle', (e, $toggle) => { + const $cell = $.closest('.data-table-cell', $toggle); + const { rowIndex } = $.data($cell); + + if ($cell.classList.contains('tree-close')) { + this.rowmanager.openTreeNode(rowIndex); + $cell.classList.remove('tree-close'); + } else { + this.rowmanager.closeTreeNode(rowIndex); + $cell.classList.add('tree-close'); + } + }); + + // this.keyboard.on('left, right', (e) => { + // const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + // if (e.target.matches('.data-table-cell')) { + // const $cell = e.target; + // const { colIndex, rowIndex } = $.data($cell); + // if (+colIndex === firstColumnIndex) { + // if (keyCode[e.keyCode] === 'left') { + // this.rowmanager.closeTreeNode(rowIndex); + // } + // if (keyCode[e.keyCode] === 'right') { + // this.rowmanager.openTreeNode(rowIndex); + // } + // return false; + // } + // } + // }); } focusCell($cell, { @@ -1994,8 +2058,8 @@ class CellManager { rowIndex } = $.data($cell); const _colIndex = this.datamanager.getColumnIndexById('_rowIndex'); - const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`; - const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; + const colHeaderSelector = `.data-table-header .data-table-cell[data-col-index="${colIndex}"]`; + const rowHeaderSelector = `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; if (this.lastHeaders) { $.removeStyle(this.lastHeaders, 'backgroundColor'); @@ -2121,7 +2185,7 @@ class CellManager { } clearSelection() { - $.each('.data-table-col.highlight', this.bodyScrollable) + $.each('.data-table-cell.highlight', this.bodyScrollable) .map(cell => cell.classList.remove('highlight')); this.$selectionCursor = null; @@ -2393,15 +2457,16 @@ class CellManager { }); return ` - - `; + + `; } getCellContent(cell) { const { - isHeader + isHeader, + isFilter } = cell; const editable = !isHeader && cell.editable !== false; @@ -2417,21 +2482,35 @@ class CellManager { const dropdown = hasDropdown ? `
${getDropdownHTML()}
` : ''; let contentHTML; - if (cell.isHeader || cell.isFilter || !cell.column.format) { + if (isHeader || isFilter || !cell.column.format) { contentHTML = cell.content; } else { contentHTML = cell.column.format(cell.content, cell); } + if (!(isHeader || isFilter) && cell.indent !== undefined) { + const nextRow = this.datamanager.getRow(cell.rowIndex + 1); + const addToggle = nextRow && nextRow.meta.indent > cell.indent; + + // Add toggle and indent in the first column + const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + if (firstColumnIndex === cell.colIndex) { + const padding = ((cell.indent || 0) + 1) * 1.5; + const toggleHTML = addToggle ? `` : ''; + contentHTML = ` + ${toggleHTML}${contentHTML}`; + } + } + return ` -
- ${(contentHTML)} - ${sortIndicator} - ${resizeColumn} - ${dropdown} -
- ${editCellHTML} - `; +
+ ${contentHTML} + ${sortIndicator} + ${resizeColumn} + ${dropdown} +
+ ${editCellHTML} + `; } getEditCellHTML() { @@ -2441,7 +2520,7 @@ class CellManager { } cellSelector(colIndex, rowIndex) { - return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; + return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } @@ -2474,8 +2553,8 @@ class RowManager { // 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'); + $.on(this.wrapper, 'click', '.data-table-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { + const $cell = $checkbox.closest('.data-table-cell'); const { rowIndex, isHeader @@ -2528,7 +2607,7 @@ class RowManager { checkRow(rowIndex, toggle) { const value = toggle ? 1 : 0; const selector = rowIndex => - `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; + `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; // update internal map this.checkMap[rowIndex] = value; // set checkbox value explicitly @@ -2550,7 +2629,7 @@ class RowManager { this.checkMap = []; } // set checkbox value - $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) + $.each('.data-table-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) .map(input => { input.checked = toggle; }); @@ -2595,8 +2674,32 @@ class RowManager { } } + hideRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.add('hide'); + }); + } + + showRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.remove('hide'); + }); + } + + openTreeNode(rowIndex) { + const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + this.showRows(rowsToShow); + } + + closeTreeNode(rowIndex) { + const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); + this.hideRows(rowsToHide); + } + getRow$(rowIndex) { - return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable); + return $(this.selector(rowIndex), this.bodyScrollable); } getTotalRows() { @@ -2662,6 +2765,10 @@ class RowManager { const dataAttr = makeDataAttributeString(props); return ``; } + + selector(rowIndex) { + return `.data-table-row[data-row-index="${rowIndex}"]`; + } } class BodyRenderer { @@ -2685,7 +2792,7 @@ class BodyRenderer { } renderBodyHTML() { - const rows = this.datamanager.getRows(); + const rows = this.datamanager.getRowsForView(); this.bodyScrollable.innerHTML = `
- ${this.getCellContent(cell)} - + ${this.getCellContent(cell)} +
@@ -2698,7 +2805,7 @@ class BodyRenderer { renderBodyWithClusterize() { // first page - const rows = this.datamanager.getRows(0, 20); + const rows = this.datamanager.getRowsForView(0, 20); const initialData = this.getDataForClusterize(rows); if (!this.clusterize) { @@ -2742,7 +2849,7 @@ class BodyRenderer { } appendRemainingData() { - const rows = this.datamanager.getRows(20); + const rows = this.datamanager.getRowsForView(20); const data = this.getDataForClusterize(rows); this.clusterize.append(data); } @@ -2859,7 +2966,7 @@ class Style { } setupMinWidth() { - $.each('.data-table-col[data-is-header]', this.header).map(col => { + $.each('.data-table-cell[data-is-header]', this.header).map(col => { const width = $.style($('.content', col), 'width'); const { colIndex @@ -2877,7 +2984,7 @@ class Style { if (!$('.data-table-row')) return; // 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 => { + $.each('.data-table-row[data-row-index="0"] .data-table-cell', this.bodyScrollable).map($cell => { const { colIndex } = $.data($cell); @@ -2926,7 +3033,7 @@ class Style { setDefaultCellHeight() { if (this.__cellHeightSet) return; const height = this.options.cellHeight || - $.style($('.data-table-col', this.instance.datatableWrapper), 'height'); + $.style($('.data-table-cell', this.instance.datatableWrapper), 'height'); if (height) { this.setCellHeight(height); this.__cellHeightSet = true; @@ -2934,10 +3041,10 @@ class Style { } setCellHeight(height) { - this.setStyle('.data-table-col .content', { + this.setStyle('.data-table-cell .content', { height: height + 'px' }); - this.setStyle('.data-table-col .edit-cell', { + this.setStyle('.data-table-cell .edit-cell', { height: height + 'px' }); } @@ -2986,7 +3093,7 @@ class Style { getColumnHeaderElement(colIndex) { colIndex = +colIndex; if (colIndex < 0) return null; - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getRowIndexColumnWidth(baseWidth) { diff --git a/docs/frappe-datatable.css b/docs/frappe-datatable.css index 30dd637..ef507fe 100644 --- a/docs/frappe-datatable.css +++ b/docs/frappe-datatable.css @@ -178,33 +178,33 @@ background-color: #f5f7fa; } -.data-table-header .data-table-col.remove-column { +.data-table-header .data-table-cell.remove-column { background-color: #FD8B8B; -webkit-transition: 300ms background-color ease-in-out; transition: 300ms background-color ease-in-out; } -.data-table-header .data-table-col.sortable-chosen { +.data-table-header .data-table-cell.sortable-chosen { background-color: #f5f7fa; } -.data-table-col { +.data-table-cell { position: relative; } -.data-table-col .content { +.data-table-cell .content { padding: 8px; padding: 0.5rem; border: 2px solid transparent; } -.data-table-col .content.ellipsis { +.data-table-cell .content.ellipsis { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } -.data-table-col .edit-cell { +.data-table-cell .edit-cell { display: none; padding: 8px; padding: 0.5rem; @@ -213,31 +213,51 @@ height: 100%; } -.data-table-col.selected .content { +.data-table-cell.selected .content { border: 2px solid rgb(82, 146, 247); } -.data-table-col.editing .content { +.data-table-cell.editing .content { display: none; } -.data-table-col.editing .edit-cell { +.data-table-cell.editing .edit-cell { border: 2px solid rgb(82, 146, 247); display: block; } -.data-table-col.highlight { +.data-table-cell.highlight { background-color: #f5f7fa; } -.data-table-col:hover .column-resizer { +.data-table-cell:hover .column-resizer { display: inline-block; } -.data-table-col:hover .data-table-dropdown-toggle { +.data-table-cell:hover .data-table-dropdown-toggle { display: block; } +.data-table-cell .tree-node { + display: inline-block; + position: relative; + } + +.data-table-cell .toggle { + display: inline-block; + position: absolute; + padding: 0 4px; + cursor: pointer; + } + +.data-table-cell .toggle:before { + content: '▼'; + } + +.data-table-cell.tree-close .toggle:before { + content: '►'; + } + .data-table-row.row-highlight { background-color: #f5f7fa; } diff --git a/docs/frappe-datatable.js b/docs/frappe-datatable.js index a5f1709..5f02555 100644 --- a/docs/frappe-datatable.js +++ b/docs/frappe-datatable.js @@ -846,6 +846,7 @@ class DataManager { this.prepareColumns(); this.prepareRows(); + this.prepareRowView(); this.prepareNumericColumns(); } @@ -963,6 +964,9 @@ class DataManager { const index = this._getNextRowCount(); let row = []; + let meta = { + rowIndex: index + }; if (Array.isArray(d)) { // row is an array @@ -989,17 +993,25 @@ class DataManager { row.push(d[col.id]); } } + + meta.indent = d.indent; } - return this.prepareRow(row, { - rowIndex: index - }); + return this.prepareRow(row, meta); }); } - prepareRow(row, props) { + prepareRowView() { + // This is order in which rows will be rendered in the table. + // When sorting happens, only this.rowViewOrder will change + // and not the original this.rows + this.rowViewOrder = this.rows.map(row => row.meta.rowIndex); + } + + prepareRow(row, meta) { const baseRowCell = { - rowIndex: props.rowIndex + rowIndex: meta.rowIndex, + indent: meta.indent }; row = row @@ -1007,7 +1019,7 @@ class DataManager { .map(cell => Object.assign({}, baseRowCell, cell)); // monkey patched in array object - row.meta = props; + row.meta = meta; return row; } @@ -1062,28 +1074,28 @@ class DataManager { (this.currentSort.sortOrder === 'asc' && sortOrder === 'desc') || (this.currentSort.sortOrder === 'desc' && sortOrder === 'asc') ) { - this.reverseArray(this.rows); + this.reverseArray(this.rowViewOrder); this.currentSort.sortOrder = sortOrder; return; } } - this.rows.sort((a, b) => { - const _aIndex = a[0].rowIndex; - const _bIndex = b[0].rowIndex; - const _a = a[colIndex].content; - const _b = b[colIndex].content; + this.rowViewOrder.sort((a, b) => { + const aIndex = a; + const bIndex = b; + const aContent = this.getCell(colIndex, a).content; + const bContent = this.getCell(colIndex, b).content; if (sortOrder === 'none') { - return _aIndex - _bIndex; + return aIndex - bIndex; } else if (sortOrder === 'asc') { - if (_a < _b) return -1; - if (_a > _b) return 1; - if (_a === _b) return 0; + if (aContent < bContent) return -1; + if (aContent > bContent) return 1; + if (aContent === bContent) return 0; } else if (sortOrder === 'desc') { - if (_a < _b) return 1; - if (_a > _b) return -1; - if (_a === _b) return 0; + if (aContent < bContent) return 1; + if (aContent > bContent) return -1; + if (aContent === bContent) return 0; } return 0; }); @@ -1092,11 +1104,9 @@ class DataManager { // update row index const srNoColIndex = this.getColumnIndexById('_rowIndex'); this.rows.forEach((row, index) => { - row.forEach(cell => { - if (cell.colIndex === srNoColIndex) { - cell.content = (index + 1) + ''; - } - }); + const viewIndex = this.rowViewOrder.indexOf(index); + const cell = row[srNoColIndex]; + cell.content = (viewIndex + 1) + ''; }); } } @@ -1259,6 +1269,11 @@ class DataManager { return this.rows.slice(start, end); } + getRowsForView(start, end) { + const rows = this.rowViewOrder.map(i => this.rows[i]); + return rows.slice(start, end); + } + getColumns(skipStandardColumns) { let columns = this.columns; @@ -1298,13 +1313,33 @@ class DataManager { getRow(rowIndex) { rowIndex = +rowIndex; - return this.rows.find(row => row[0].rowIndex === rowIndex); + return this.rows[rowIndex]; } getCell(colIndex, rowIndex) { rowIndex = +rowIndex; colIndex = +colIndex; - return this.rows.find(row => row[0].rowIndex === rowIndex)[colIndex]; + return this.getRow(rowIndex)[colIndex]; + } + + getChildrenIndices(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + + let i = parentRowIndex + 1; + let nextRow = this.getRow(i); + let nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + + while (nextIndent > parentIndent) { + out.push(i); + + i++; + nextRow = this.getRow(i); + nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + } + + return out; } get() { @@ -1364,11 +1399,11 @@ class ColumnManager { refreshHeader() { const columns = this.datamanager.getColumns(); - const $cols = $.each('.data-table-col[data-is-header]', this.header); + const $cols = $.each('.data-table-cell[data-is-header]', this.header); const refreshHTML = // first init - !$('.data-table-col', this.header) || + !$('.data-table-cell', this.header) || // deleted column columns.length < $cols.length; @@ -1377,7 +1412,9 @@ class ColumnManager { $('thead', this.header).innerHTML = this.getHeaderHTML(columns); this.$filterRow = $('.data-table-row[data-is-filter]', this.header); - $.style(this.$filterRow, { display: 'none' }); + if (this.$filterRow) { + $.style(this.$filterRow, { display: 'none' }); + } } else { // update data-attributes $cols.map(($col, i) => { @@ -1440,7 +1477,7 @@ class ColumnManager { const dropdownItems = this.options.headerDropdown; $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => { - const $col = $.closest('.data-table-col', $item); + const $col = $.closest('.data-table-cell', $item); const { index } = $.data($item); @@ -1462,7 +1499,7 @@ class ColumnManager { let isDragging = false; let $resizingCell, startWidth, startX; - $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => { + $.on(this.header, 'mousedown', '.data-table-cell .column-resizer', (e, $handle) => { document.body.classList.add('data-table-resize'); const $cell = $handle.parentNode.parentNode; $resizingCell = $cell; @@ -1519,7 +1556,7 @@ class ColumnManager { $.off(document.body, 'mousemove', initialize); return; } - const ready = $('.data-table-col', this.header); + const ready = $('.data-table-cell', this.header); if (!ready) return; const $parent = $('.data-table-row', this.header); @@ -1549,8 +1586,8 @@ class ColumnManager { bindSortColumn() { - $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => { - const $cell = span.closest('.data-table-col'); + $.on(this.header, 'click', '.data-table-cell .column-title', (e, span) => { + const $cell = span.closest('.data-table-cell'); let { colIndex, sortOrder @@ -1564,7 +1601,7 @@ class ColumnManager { // reset sort indicator $('.sort-indicator', this.header).textContent = ''; - $.each('.data-table-col', this.header).map($cell => { + $.each('.data-table-cell', this.header).map($cell => { $.data($cell, { sortOrder: 'none' }); @@ -1665,7 +1702,7 @@ class ColumnManager { bindFilter() { if (!this.options.enableInlineFilters) return; const handler = e => { - const $filterCell = $.closest('.data-table-col', e.target); + const $filterCell = $.closest('.data-table-cell', e.target); const { colIndex } = $.data($filterCell); @@ -1676,14 +1713,8 @@ class ColumnManager { 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'); - }); + this.rowmanager.hideRows(rowsToHide); + this.rowmanager.showRows(rowsToShow); }); }; $.on(this.header, 'keydown', '.data-table-filter', debounce$2(handler, 300)); @@ -1754,7 +1785,7 @@ class ColumnManager { } getHeaderCell$(colIndex) { - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getLastColumnIndex() { @@ -1801,6 +1832,7 @@ class CellManager { this.bindKeyboardSelection(); this.bindCopyCellContents(); this.bindMouseEvents(); + this.bindTreeEvents(); } bindFocusCell() { @@ -1810,7 +1842,7 @@ class CellManager { bindEditCell() { this.$editingCell = null; - $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => { + $.on(this.bodyScrollable, 'dblclick', '.data-table-cell', (e, cell) => { this.activateEditing(cell); }); @@ -1887,7 +1919,7 @@ class CellManager { if (this.options.enableInlineFilters) { this.keyboard.on('ctrl+f', (e) => { - const $cell = $.closest('.data-table-col', e.target); + const $cell = $.closest('.data-table-cell', e.target); let { colIndex } = $.data($cell); @@ -1930,7 +1962,7 @@ class CellManager { bindMouseEvents() { let mouseDown = null; - $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => { + $.on(this.bodyScrollable, 'mousedown', '.data-table-cell', (e) => { mouseDown = true; this.focusCell($(e.delegatedTarget)); }); @@ -1944,7 +1976,39 @@ class CellManager { this.selectArea($(e.delegatedTarget)); }; - $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle$1(selectArea, 50)); + $.on(this.bodyScrollable, 'mousemove', '.data-table-cell', throttle$1(selectArea, 50)); + } + + bindTreeEvents() { + $.on(this.bodyScrollable, 'click', '.toggle', (e, $toggle) => { + const $cell = $.closest('.data-table-cell', $toggle); + const { rowIndex } = $.data($cell); + + if ($cell.classList.contains('tree-close')) { + this.rowmanager.openTreeNode(rowIndex); + $cell.classList.remove('tree-close'); + } else { + this.rowmanager.closeTreeNode(rowIndex); + $cell.classList.add('tree-close'); + } + }); + + // this.keyboard.on('left, right', (e) => { + // const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + // if (e.target.matches('.data-table-cell')) { + // const $cell = e.target; + // const { colIndex, rowIndex } = $.data($cell); + // if (+colIndex === firstColumnIndex) { + // if (keyCode[e.keyCode] === 'left') { + // this.rowmanager.closeTreeNode(rowIndex); + // } + // if (keyCode[e.keyCode] === 'right') { + // this.rowmanager.openTreeNode(rowIndex); + // } + // return false; + // } + // } + // }); } focusCell($cell, { @@ -1994,8 +2058,8 @@ class CellManager { rowIndex } = $.data($cell); const _colIndex = this.datamanager.getColumnIndexById('_rowIndex'); - const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`; - const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; + const colHeaderSelector = `.data-table-header .data-table-cell[data-col-index="${colIndex}"]`; + const rowHeaderSelector = `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; if (this.lastHeaders) { $.removeStyle(this.lastHeaders, 'backgroundColor'); @@ -2121,7 +2185,7 @@ class CellManager { } clearSelection() { - $.each('.data-table-col.highlight', this.bodyScrollable) + $.each('.data-table-cell.highlight', this.bodyScrollable) .map(cell => cell.classList.remove('highlight')); this.$selectionCursor = null; @@ -2393,15 +2457,16 @@ class CellManager { }); return ` - - `; + + `; } getCellContent(cell) { const { - isHeader + isHeader, + isFilter } = cell; const editable = !isHeader && cell.editable !== false; @@ -2417,21 +2482,35 @@ class CellManager { const dropdown = hasDropdown ? `
${getDropdownHTML()}
` : ''; let contentHTML; - if (cell.isHeader || cell.isFilter || !cell.column.format) { + if (isHeader || isFilter || !cell.column.format) { contentHTML = cell.content; } else { contentHTML = cell.column.format(cell.content, cell); } + if (!(isHeader || isFilter) && cell.indent !== undefined) { + const nextRow = this.datamanager.getRow(cell.rowIndex + 1); + const addToggle = nextRow && nextRow.meta.indent > cell.indent; + + // Add toggle and indent in the first column + const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + if (firstColumnIndex === cell.colIndex) { + const padding = ((cell.indent || 0) + 1) * 1.5; + const toggleHTML = addToggle ? `` : ''; + contentHTML = ` + ${toggleHTML}${contentHTML}`; + } + } + return ` -
- ${(contentHTML)} - ${sortIndicator} - ${resizeColumn} - ${dropdown} -
- ${editCellHTML} - `; +
+ ${contentHTML} + ${sortIndicator} + ${resizeColumn} + ${dropdown} +
+ ${editCellHTML} + `; } getEditCellHTML() { @@ -2441,7 +2520,7 @@ class CellManager { } cellSelector(colIndex, rowIndex) { - return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; + return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } @@ -2474,8 +2553,8 @@ class RowManager { // 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'); + $.on(this.wrapper, 'click', '.data-table-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { + const $cell = $checkbox.closest('.data-table-cell'); const { rowIndex, isHeader @@ -2528,7 +2607,7 @@ class RowManager { checkRow(rowIndex, toggle) { const value = toggle ? 1 : 0; const selector = rowIndex => - `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; + `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; // update internal map this.checkMap[rowIndex] = value; // set checkbox value explicitly @@ -2550,7 +2629,7 @@ class RowManager { this.checkMap = []; } // set checkbox value - $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) + $.each('.data-table-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) .map(input => { input.checked = toggle; }); @@ -2595,8 +2674,32 @@ class RowManager { } } + hideRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.add('hide'); + }); + } + + showRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.remove('hide'); + }); + } + + openTreeNode(rowIndex) { + const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + this.showRows(rowsToShow); + } + + closeTreeNode(rowIndex) { + const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); + this.hideRows(rowsToHide); + } + getRow$(rowIndex) { - return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable); + return $(this.selector(rowIndex), this.bodyScrollable); } getTotalRows() { @@ -2662,6 +2765,10 @@ class RowManager { const dataAttr = makeDataAttributeString(props); return ``; } + + selector(rowIndex) { + return `.data-table-row[data-row-index="${rowIndex}"]`; + } } class BodyRenderer { @@ -2685,7 +2792,7 @@ class BodyRenderer { } renderBodyHTML() { - const rows = this.datamanager.getRows(); + const rows = this.datamanager.getRowsForView(); this.bodyScrollable.innerHTML = `
- ${this.getCellContent(cell)} - + ${this.getCellContent(cell)} +
@@ -2698,7 +2805,7 @@ class BodyRenderer { renderBodyWithClusterize() { // first page - const rows = this.datamanager.getRows(0, 20); + const rows = this.datamanager.getRowsForView(0, 20); const initialData = this.getDataForClusterize(rows); if (!this.clusterize) { @@ -2742,7 +2849,7 @@ class BodyRenderer { } appendRemainingData() { - const rows = this.datamanager.getRows(20); + const rows = this.datamanager.getRowsForView(20); const data = this.getDataForClusterize(rows); this.clusterize.append(data); } @@ -2859,7 +2966,7 @@ class Style { } setupMinWidth() { - $.each('.data-table-col[data-is-header]', this.header).map(col => { + $.each('.data-table-cell[data-is-header]', this.header).map(col => { const width = $.style($('.content', col), 'width'); const { colIndex @@ -2877,7 +2984,7 @@ class Style { if (!$('.data-table-row')) return; // 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 => { + $.each('.data-table-row[data-row-index="0"] .data-table-cell', this.bodyScrollable).map($cell => { const { colIndex } = $.data($cell); @@ -2926,7 +3033,7 @@ class Style { setDefaultCellHeight() { if (this.__cellHeightSet) return; const height = this.options.cellHeight || - $.style($('.data-table-col', this.instance.datatableWrapper), 'height'); + $.style($('.data-table-cell', this.instance.datatableWrapper), 'height'); if (height) { this.setCellHeight(height); this.__cellHeightSet = true; @@ -2934,10 +3041,10 @@ class Style { } setCellHeight(height) { - this.setStyle('.data-table-col .content', { + this.setStyle('.data-table-cell .content', { height: height + 'px' }); - this.setStyle('.data-table-col .edit-cell', { + this.setStyle('.data-table-cell .edit-cell', { height: height + 'px' }); } @@ -2986,7 +3093,7 @@ class Style { getColumnHeaderElement(colIndex) { colIndex = +colIndex; if (colIndex < 0) return null; - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getRowIndexColumnWidth(baseWidth) { diff --git a/docs/index.js b/docs/index.js index 372b4af..224fc81 100644 --- a/docs/index.js +++ b/docs/index.js @@ -12,7 +12,8 @@ let datatable1 = new DataTable('.datatable-1', { }); let datatable2 = new DataTable('.datatable-2', Object.assign(getTreeData(), { - enableInlineFilters: true + enableInlineFilters: true, + addCheckboxColumn: true })); function getSampleData(multiplier) { diff --git a/index.html b/index.html index a4c587b..48a23d0 100644 --- a/index.html +++ b/index.html @@ -54,7 +54,7 @@ if (largeData) { for (let i = 0; i < 10; i++) { - data = data.concat(data.rows); + data = data.concat(data); } } } diff --git a/src/cellmanager.js b/src/cellmanager.js index b8ea6c3..b7372ab 100644 --- a/src/cellmanager.js +++ b/src/cellmanager.js @@ -29,6 +29,7 @@ export default class CellManager { this.bindKeyboardSelection(); this.bindCopyCellContents(); this.bindMouseEvents(); + this.bindTreeEvents(); } bindFocusCell() { @@ -38,7 +39,7 @@ export default class CellManager { bindEditCell() { this.$editingCell = null; - $.on(this.bodyScrollable, 'dblclick', '.data-table-col', (e, cell) => { + $.on(this.bodyScrollable, 'dblclick', '.data-table-cell', (e, cell) => { this.activateEditing(cell); }); @@ -115,7 +116,7 @@ export default class CellManager { if (this.options.enableInlineFilters) { this.keyboard.on('ctrl+f', (e) => { - const $cell = $.closest('.data-table-col', e.target); + const $cell = $.closest('.data-table-cell', e.target); let { colIndex } = $.data($cell); @@ -158,7 +159,7 @@ export default class CellManager { bindMouseEvents() { let mouseDown = null; - $.on(this.bodyScrollable, 'mousedown', '.data-table-col', (e) => { + $.on(this.bodyScrollable, 'mousedown', '.data-table-cell', (e) => { mouseDown = true; this.focusCell($(e.delegatedTarget)); }); @@ -172,7 +173,39 @@ export default class CellManager { this.selectArea($(e.delegatedTarget)); }; - $.on(this.bodyScrollable, 'mousemove', '.data-table-col', throttle(selectArea, 50)); + $.on(this.bodyScrollable, 'mousemove', '.data-table-cell', throttle(selectArea, 50)); + } + + bindTreeEvents() { + $.on(this.bodyScrollable, 'click', '.toggle', (e, $toggle) => { + const $cell = $.closest('.data-table-cell', $toggle); + const { rowIndex } = $.data($cell); + + if ($cell.classList.contains('tree-close')) { + this.rowmanager.openTreeNode(rowIndex); + $cell.classList.remove('tree-close'); + } else { + this.rowmanager.closeTreeNode(rowIndex); + $cell.classList.add('tree-close'); + } + }); + + // this.keyboard.on('left, right', (e) => { + // const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + // if (e.target.matches('.data-table-cell')) { + // const $cell = e.target; + // const { colIndex, rowIndex } = $.data($cell); + // if (+colIndex === firstColumnIndex) { + // if (keyCode[e.keyCode] === 'left') { + // this.rowmanager.closeTreeNode(rowIndex); + // } + // if (keyCode[e.keyCode] === 'right') { + // this.rowmanager.openTreeNode(rowIndex); + // } + // return false; + // } + // } + // }); } focusCell($cell, { @@ -222,8 +255,8 @@ export default class CellManager { rowIndex } = $.data($cell); const _colIndex = this.datamanager.getColumnIndexById('_rowIndex'); - const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`; - const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; + const colHeaderSelector = `.data-table-header .data-table-cell[data-col-index="${colIndex}"]`; + const rowHeaderSelector = `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`; if (this.lastHeaders) { $.removeStyle(this.lastHeaders, 'backgroundColor'); @@ -349,7 +382,7 @@ export default class CellManager { } clearSelection() { - $.each('.data-table-col.highlight', this.bodyScrollable) + $.each('.data-table-cell.highlight', this.bodyScrollable) .map(cell => cell.classList.remove('highlight')); this.$selectionCursor = null; @@ -621,15 +654,16 @@ export default class CellManager { }); return ` - - `; + + `; } getCellContent(cell) { const { - isHeader + isHeader, + isFilter } = cell; const editable = !isHeader && cell.editable !== false; @@ -645,21 +679,35 @@ export default class CellManager { const dropdown = hasDropdown ? `
${getDropdownHTML()}
` : ''; let contentHTML; - if (cell.isHeader || cell.isFilter || !cell.column.format) { + if (isHeader || isFilter || !cell.column.format) { contentHTML = cell.content; } else { contentHTML = cell.column.format(cell.content, cell); } + if (!(isHeader || isFilter) && cell.indent !== undefined) { + const nextRow = this.datamanager.getRow(cell.rowIndex + 1); + const addToggle = nextRow && nextRow.meta.indent > cell.indent; + + // Add toggle and indent in the first column + const firstColumnIndex = this.datamanager.getColumnIndexById('_rowIndex') + 1; + if (firstColumnIndex === cell.colIndex) { + const padding = ((cell.indent || 0) + 1) * 1.5; + const toggleHTML = addToggle ? `` : ''; + contentHTML = ` + ${toggleHTML}${contentHTML}`; + } + } + return ` -
- ${(contentHTML)} - ${sortIndicator} - ${resizeColumn} - ${dropdown} -
- ${editCellHTML} - `; +
+ ${contentHTML} + ${sortIndicator} + ${resizeColumn} + ${dropdown} +
+ ${editCellHTML} + `; } getEditCellHTML() { @@ -669,6 +717,6 @@ export default class CellManager { } cellSelector(colIndex, rowIndex) { - return `.data-table-col[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; + return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } diff --git a/src/columnmanager.js b/src/columnmanager.js index 031e0b9..da71147 100644 --- a/src/columnmanager.js +++ b/src/columnmanager.js @@ -32,11 +32,11 @@ export default class ColumnManager { refreshHeader() { const columns = this.datamanager.getColumns(); - const $cols = $.each('.data-table-col[data-is-header]', this.header); + const $cols = $.each('.data-table-cell[data-is-header]', this.header); const refreshHTML = // first init - !$('.data-table-col', this.header) || + !$('.data-table-cell', this.header) || // deleted column columns.length < $cols.length; @@ -45,7 +45,9 @@ export default class ColumnManager { $('thead', this.header).innerHTML = this.getHeaderHTML(columns); this.$filterRow = $('.data-table-row[data-is-filter]', this.header); - $.style(this.$filterRow, { display: 'none' }); + if (this.$filterRow) { + $.style(this.$filterRow, { display: 'none' }); + } } else { // update data-attributes $cols.map(($col, i) => { @@ -108,7 +110,7 @@ export default class ColumnManager { const dropdownItems = this.options.headerDropdown; $.on(this.header, 'click', '.data-table-dropdown-list > div', (e, $item) => { - const $col = $.closest('.data-table-col', $item); + const $col = $.closest('.data-table-cell', $item); const { index } = $.data($item); @@ -130,7 +132,7 @@ export default class ColumnManager { let isDragging = false; let $resizingCell, startWidth, startX; - $.on(this.header, 'mousedown', '.data-table-col .column-resizer', (e, $handle) => { + $.on(this.header, 'mousedown', '.data-table-cell .column-resizer', (e, $handle) => { document.body.classList.add('data-table-resize'); const $cell = $handle.parentNode.parentNode; $resizingCell = $cell; @@ -187,7 +189,7 @@ export default class ColumnManager { $.off(document.body, 'mousemove', initialize); return; } - const ready = $('.data-table-col', this.header); + const ready = $('.data-table-cell', this.header); if (!ready) return; const $parent = $('.data-table-row', this.header); @@ -217,8 +219,8 @@ export default class ColumnManager { bindSortColumn() { - $.on(this.header, 'click', '.data-table-col .column-title', (e, span) => { - const $cell = span.closest('.data-table-col'); + $.on(this.header, 'click', '.data-table-cell .column-title', (e, span) => { + const $cell = span.closest('.data-table-cell'); let { colIndex, sortOrder @@ -232,7 +234,7 @@ export default class ColumnManager { // reset sort indicator $('.sort-indicator', this.header).textContent = ''; - $.each('.data-table-col', this.header).map($cell => { + $.each('.data-table-cell', this.header).map($cell => { $.data($cell, { sortOrder: 'none' }); @@ -333,7 +335,7 @@ export default class ColumnManager { bindFilter() { if (!this.options.enableInlineFilters) return; const handler = e => { - const $filterCell = $.closest('.data-table-col', e.target); + const $filterCell = $.closest('.data-table-cell', e.target); const { colIndex } = $.data($filterCell); @@ -344,14 +346,8 @@ export default class ColumnManager { 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'); - }); + this.rowmanager.hideRows(rowsToHide); + this.rowmanager.showRows(rowsToShow); }); }; $.on(this.header, 'keydown', '.data-table-filter', debounce(handler, 300)); @@ -422,7 +418,7 @@ export default class ColumnManager { } getHeaderCell$(colIndex) { - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getLastColumnIndex() { diff --git a/src/datamanager.js b/src/datamanager.js index 5af6209..2bbb969 100644 --- a/src/datamanager.js +++ b/src/datamanager.js @@ -501,6 +501,26 @@ export default class DataManager { return this.getRow(rowIndex)[colIndex]; } + getChildrenIndices(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + + let i = parentRowIndex + 1; + let nextRow = this.getRow(i); + let nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + + while (nextIndent > parentIndent) { + out.push(i); + + i++; + nextRow = this.getRow(i); + nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + } + + return out; + } + get() { return { columns: this.columns, diff --git a/src/keyboard.js b/src/keyboard.js index 77f2cbe..6f5c385 100644 --- a/src/keyboard.js +++ b/src/keyboard.js @@ -54,3 +54,5 @@ export default class Keyboard { }); } } + +export let keyCode = KEYCODES; diff --git a/src/rowmanager.js b/src/rowmanager.js index 21caaa2..99b624e 100644 --- a/src/rowmanager.js +++ b/src/rowmanager.js @@ -33,8 +33,8 @@ export default class RowManager { // 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'); + $.on(this.wrapper, 'click', '.data-table-cell[data-col-index="0"] [type="checkbox"]', (e, $checkbox) => { + const $cell = $checkbox.closest('.data-table-cell'); const { rowIndex, isHeader @@ -87,7 +87,7 @@ export default class RowManager { checkRow(rowIndex, toggle) { const value = toggle ? 1 : 0; const selector = rowIndex => - `.data-table-col[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; + `.data-table-cell[data-row-index="${rowIndex}"][data-col-index="0"] [type="checkbox"]`; // update internal map this.checkMap[rowIndex] = value; // set checkbox value explicitly @@ -109,7 +109,7 @@ export default class RowManager { this.checkMap = []; } // set checkbox value - $.each('.data-table-col[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) + $.each('.data-table-cell[data-col-index="0"] [type="checkbox"]', this.bodyScrollable) .map(input => { input.checked = toggle; }); @@ -154,8 +154,32 @@ export default class RowManager { } } + hideRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.add('hide'); + }); + } + + showRows(rowIndices) { + rowIndices.map(rowIndex => { + const $tr = this.getRow$(rowIndex); + $tr.classList.remove('hide'); + }); + } + + openTreeNode(rowIndex) { + const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + this.showRows(rowsToShow); + } + + closeTreeNode(rowIndex) { + const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); + this.hideRows(rowsToHide); + } + getRow$(rowIndex) { - return $(`.data-table-row[data-row-index="${rowIndex}"]`, this.bodyScrollable); + return $(this.selector(rowIndex), this.bodyScrollable); } getTotalRows() { @@ -221,4 +245,8 @@ export default class RowManager { const dataAttr = makeDataAttributeString(props); return ``; } + + selector(rowIndex) { + return `.data-table-row[data-row-index="${rowIndex}"]`; + } } diff --git a/src/style.css b/src/style.css index 9cd2d1a..f084e82 100644 --- a/src/style.css +++ b/src/style.css @@ -173,17 +173,17 @@ } } - .data-table-col.remove-column { + .data-table-cell.remove-column { background-color: var(--light-red); transition: 300ms background-color ease-in-out; } - .data-table-col.sortable-chosen { + .data-table-cell.sortable-chosen { background-color: var(--light-bg); } } -.data-table-col { +.data-table-cell { position: relative; .content { @@ -231,6 +231,28 @@ &:hover .data-table-dropdown-toggle { display: block; } + + .tree-node { + display: inline-block; + position: relative; + } + + .toggle { + display: inline-block; + position: absolute; + padding: 0 4px; + cursor: pointer; + } + + .toggle:before { + content: '▼'; + } +} + +.data-table-cell.tree-close { + .toggle:before { + content: '►'; + } } .data-table-row { diff --git a/src/style.js b/src/style.js index a0c5507..24117c8 100644 --- a/src/style.js +++ b/src/style.js @@ -104,7 +104,7 @@ export default class Style { } setupMinWidth() { - $.each('.data-table-col[data-is-header]', this.header).map(col => { + $.each('.data-table-cell[data-is-header]', this.header).map(col => { const width = $.style($('.content', col), 'width'); const { colIndex @@ -122,7 +122,7 @@ export default class Style { if (!$('.data-table-row')) return; // 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 => { + $.each('.data-table-row[data-row-index="0"] .data-table-cell', this.bodyScrollable).map($cell => { const { colIndex } = $.data($cell); @@ -171,7 +171,7 @@ export default class Style { setDefaultCellHeight() { if (this.__cellHeightSet) return; const height = this.options.cellHeight || - $.style($('.data-table-col', this.instance.datatableWrapper), 'height'); + $.style($('.data-table-cell', this.instance.datatableWrapper), 'height'); if (height) { this.setCellHeight(height); this.__cellHeightSet = true; @@ -179,10 +179,10 @@ export default class Style { } setCellHeight(height) { - this.setStyle('.data-table-col .content', { + this.setStyle('.data-table-cell .content', { height: height + 'px' }); - this.setStyle('.data-table-col .edit-cell', { + this.setStyle('.data-table-cell .edit-cell', { height: height + 'px' }); } @@ -231,7 +231,7 @@ export default class Style { getColumnHeaderElement(colIndex) { colIndex = +colIndex; if (colIndex < 0) return null; - return $(`.data-table-col[data-col-index="${colIndex}"]`, this.header); + return $(`.data-table-cell[data-col-index="${colIndex}"]`, this.header); } getRowIndexColumnWidth(baseWidth) {
- ${this.getCellContent(cell)} - + ${this.getCellContent(cell)} +