diff --git a/dist/frappe-datatable.cjs.js b/dist/frappe-datatable.cjs.js index 9447593..05459d0 100644 --- a/dist/frappe-datatable.cjs.js +++ b/dist/frappe-datatable.cjs.js @@ -825,6 +825,25 @@ function linkProperties(target, source, properties) { Object.defineProperties(target, props); } +function isSet(val) { + return val !== undefined || val !== null; +} + +function notSet(val) { + return !isSet(val); +} + +function isNumber(val) { + return !isNaN(val); +} + +function ensureArray(val) { + if (!Array.isArray(val)) { + return [val]; + } + return val; +} + class DataManager { constructor(options) { this.options = options; @@ -847,6 +866,7 @@ class DataManager { this.prepareColumns(); this.prepareRows(); + this.prepareTreeRows(); this.prepareRowView(); this.prepareNumericColumns(); @@ -995,13 +1015,25 @@ class DataManager { } } - meta.indent = d.indent; + meta.indent = d.indent || 0; } return this.prepareRow(row, meta); }); } + prepareTreeRows() { + this.rows.forEach((row, i) => { + if (isNumber(row.meta.indent)) { + // if (i === 36) debugger; + const nextRow = this.getRow(i + 1); + row.meta.isLeaf = !nextRow || + notSet(nextRow.meta.indent) || + nextRow.meta.indent <= row.meta.indent; + } + }); + } + prepareRowView() { // This is order in which rows will be rendered in the table. // When sorting happens, only this.rowViewOrder will change @@ -1323,21 +1355,44 @@ class DataManager { return this.getRow(rowIndex)[colIndex]; } - getChildrenIndices(parentRowIndex) { + getChildren(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; + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent)) continue; - while (nextIndent > parentIndent) { - out.push(i); + if (row.meta.indent > parentIndent) { + out.push(i); + } - i++; - nextRow = this.getRow(i); - nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + getImmediateChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + const childIndent = parentIndent + 1; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; + + if (row.meta.indent === childIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } } return out; @@ -1774,15 +1829,7 @@ class ColumnManager { } getFirstColumnIndex() { - if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) { - return 2; - } - - if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) { - return 1; - } - - return 0; + return this.datamanager.getColumnIndexById('_rowIndex') + 1; } getHeaderCell$(colIndex) { @@ -1815,14 +1862,16 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') { class CellManager { constructor(instance) { this.instance = instance; - this.wrapper = this.instance.wrapper; - this.options = this.instance.options; - this.style = this.instance.style; - this.bodyScrollable = this.instance.bodyScrollable; - this.columnmanager = this.instance.columnmanager; - this.rowmanager = this.instance.rowmanager; - this.datamanager = this.instance.datamanager; - this.keyboard = this.instance.keyboard; + linkProperties(this, this.instance, [ + 'wrapper', + 'options', + 'style', + 'bodyScrollable', + 'columnmanager', + 'rowmanager', + 'datamanager', + 'keyboard' + ]); this.bindEvents(); } @@ -1986,30 +2035,11 @@ class CellManager { const { rowIndex } = $.data($cell); if ($cell.classList.contains('tree-close')) { - this.rowmanager.openTreeNode(rowIndex); - $cell.classList.remove('tree-close'); + this.rowmanager.openSingleNode(rowIndex); } else { - this.rowmanager.closeTreeNode(rowIndex); - $cell.classList.add('tree-close'); + this.rowmanager.closeSingleNode(rowIndex); } }); - - // 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, { @@ -2362,17 +2392,25 @@ class CellManager { } refreshCell(cell) { - const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable); + const $cell = $(this.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); $cell.innerHTML = this.getCellContent(cell); } + toggleTreeButton(rowIndex, flag) { + const colIndex = this.columnmanager.getFirstColumnIndex(); + const $cell = this.getCell$(colIndex, rowIndex); + if ($cell) { + $cell.classList[flag ? 'remove' : 'add']('tree-close'); + } + } + isStandardCell(colIndex) { // Standard cells are in Sr. No and Checkbox column return colIndex < this.columnmanager.getFirstColumnIndex(); } getCell$(colIndex, rowIndex) { - return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable); + return $(this.selector(colIndex, rowIndex), this.bodyScrollable); } getAboveCell$($cell) { @@ -2520,7 +2558,7 @@ class CellManager { `; } - cellSelector(colIndex, rowIndex) { + selector(colIndex, rowIndex) { return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } @@ -2528,9 +2566,11 @@ class CellManager { class RowManager { constructor(instance) { this.instance = instance; - this.options = this.instance.options; - this.wrapper = this.instance.wrapper; - this.bodyScrollable = this.instance.bodyScrollable; + linkProperties(this, this.instance, [ + 'options', + 'wrapper', + 'bodyScrollable' + ]); this.bindEvents(); this.refreshRows = promisify(this.refreshRows, this); @@ -2676,6 +2716,7 @@ class RowManager { } hideRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.add('hide'); @@ -2683,20 +2724,33 @@ class RowManager { } showRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.remove('hide'); }); } - openTreeNode(rowIndex) { - const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + openSingleNode(rowIndex) { + const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); this.showRows(rowsToShow); + this.cellmanager.toggleTreeButton(rowIndex, true); } - closeTreeNode(rowIndex) { - const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); - this.hideRows(rowsToHide); + closeSingleNode(rowIndex) { + const children = this.datamanager.getImmediateChildren(rowIndex); + children.forEach(childIndex => { + const row = this.datamanager.getRow(childIndex); + if (row.meta.isLeaf) { + // close + this.hideRows(childIndex); + this.cellmanager.toggleTreeButton(childIndex, false); + } else { + this.closeSingleNode(childIndex); + this.hideRows(childIndex); + } + }); + this.cellmanager.toggleTreeButton(rowIndex, false); } getRow$(rowIndex) { diff --git a/dist/frappe-datatable.js b/dist/frappe-datatable.js index 5f02555..f0d4581 100644 --- a/dist/frappe-datatable.js +++ b/dist/frappe-datatable.js @@ -824,6 +824,25 @@ function linkProperties(target, source, properties) { Object.defineProperties(target, props); } +function isSet(val) { + return val !== undefined || val !== null; +} + +function notSet(val) { + return !isSet(val); +} + +function isNumber(val) { + return !isNaN(val); +} + +function ensureArray(val) { + if (!Array.isArray(val)) { + return [val]; + } + return val; +} + class DataManager { constructor(options) { this.options = options; @@ -846,6 +865,7 @@ class DataManager { this.prepareColumns(); this.prepareRows(); + this.prepareTreeRows(); this.prepareRowView(); this.prepareNumericColumns(); @@ -994,13 +1014,25 @@ class DataManager { } } - meta.indent = d.indent; + meta.indent = d.indent || 0; } return this.prepareRow(row, meta); }); } + prepareTreeRows() { + this.rows.forEach((row, i) => { + if (isNumber(row.meta.indent)) { + // if (i === 36) debugger; + const nextRow = this.getRow(i + 1); + row.meta.isLeaf = !nextRow || + notSet(nextRow.meta.indent) || + nextRow.meta.indent <= row.meta.indent; + } + }); + } + prepareRowView() { // This is order in which rows will be rendered in the table. // When sorting happens, only this.rowViewOrder will change @@ -1322,21 +1354,44 @@ class DataManager { return this.getRow(rowIndex)[colIndex]; } - getChildrenIndices(parentRowIndex) { + getChildren(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; + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent)) continue; - while (nextIndent > parentIndent) { - out.push(i); + if (row.meta.indent > parentIndent) { + out.push(i); + } - i++; - nextRow = this.getRow(i); - nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + getImmediateChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + const childIndent = parentIndent + 1; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; + + if (row.meta.indent === childIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } } return out; @@ -1773,15 +1828,7 @@ class ColumnManager { } getFirstColumnIndex() { - if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) { - return 2; - } - - if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) { - return 1; - } - - return 0; + return this.datamanager.getColumnIndexById('_rowIndex') + 1; } getHeaderCell$(colIndex) { @@ -1814,14 +1861,16 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') { class CellManager { constructor(instance) { this.instance = instance; - this.wrapper = this.instance.wrapper; - this.options = this.instance.options; - this.style = this.instance.style; - this.bodyScrollable = this.instance.bodyScrollable; - this.columnmanager = this.instance.columnmanager; - this.rowmanager = this.instance.rowmanager; - this.datamanager = this.instance.datamanager; - this.keyboard = this.instance.keyboard; + linkProperties(this, this.instance, [ + 'wrapper', + 'options', + 'style', + 'bodyScrollable', + 'columnmanager', + 'rowmanager', + 'datamanager', + 'keyboard' + ]); this.bindEvents(); } @@ -1985,30 +2034,11 @@ class CellManager { const { rowIndex } = $.data($cell); if ($cell.classList.contains('tree-close')) { - this.rowmanager.openTreeNode(rowIndex); - $cell.classList.remove('tree-close'); + this.rowmanager.openSingleNode(rowIndex); } else { - this.rowmanager.closeTreeNode(rowIndex); - $cell.classList.add('tree-close'); + this.rowmanager.closeSingleNode(rowIndex); } }); - - // 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, { @@ -2361,17 +2391,25 @@ class CellManager { } refreshCell(cell) { - const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable); + const $cell = $(this.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); $cell.innerHTML = this.getCellContent(cell); } + toggleTreeButton(rowIndex, flag) { + const colIndex = this.columnmanager.getFirstColumnIndex(); + const $cell = this.getCell$(colIndex, rowIndex); + if ($cell) { + $cell.classList[flag ? 'remove' : 'add']('tree-close'); + } + } + isStandardCell(colIndex) { // Standard cells are in Sr. No and Checkbox column return colIndex < this.columnmanager.getFirstColumnIndex(); } getCell$(colIndex, rowIndex) { - return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable); + return $(this.selector(colIndex, rowIndex), this.bodyScrollable); } getAboveCell$($cell) { @@ -2519,7 +2557,7 @@ class CellManager { `; } - cellSelector(colIndex, rowIndex) { + selector(colIndex, rowIndex) { return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } @@ -2527,9 +2565,11 @@ class CellManager { class RowManager { constructor(instance) { this.instance = instance; - this.options = this.instance.options; - this.wrapper = this.instance.wrapper; - this.bodyScrollable = this.instance.bodyScrollable; + linkProperties(this, this.instance, [ + 'options', + 'wrapper', + 'bodyScrollable' + ]); this.bindEvents(); this.refreshRows = promisify(this.refreshRows, this); @@ -2675,6 +2715,7 @@ class RowManager { } hideRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.add('hide'); @@ -2682,20 +2723,33 @@ class RowManager { } showRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.remove('hide'); }); } - openTreeNode(rowIndex) { - const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + openSingleNode(rowIndex) { + const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); this.showRows(rowsToShow); + this.cellmanager.toggleTreeButton(rowIndex, true); } - closeTreeNode(rowIndex) { - const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); - this.hideRows(rowsToHide); + closeSingleNode(rowIndex) { + const children = this.datamanager.getImmediateChildren(rowIndex); + children.forEach(childIndex => { + const row = this.datamanager.getRow(childIndex); + if (row.meta.isLeaf) { + // close + this.hideRows(childIndex); + this.cellmanager.toggleTreeButton(childIndex, false); + } else { + this.closeSingleNode(childIndex); + this.hideRows(childIndex); + } + }); + this.cellmanager.toggleTreeButton(rowIndex, false); } getRow$(rowIndex) { diff --git a/docs/frappe-datatable.js b/docs/frappe-datatable.js index 5f02555..f0d4581 100644 --- a/docs/frappe-datatable.js +++ b/docs/frappe-datatable.js @@ -824,6 +824,25 @@ function linkProperties(target, source, properties) { Object.defineProperties(target, props); } +function isSet(val) { + return val !== undefined || val !== null; +} + +function notSet(val) { + return !isSet(val); +} + +function isNumber(val) { + return !isNaN(val); +} + +function ensureArray(val) { + if (!Array.isArray(val)) { + return [val]; + } + return val; +} + class DataManager { constructor(options) { this.options = options; @@ -846,6 +865,7 @@ class DataManager { this.prepareColumns(); this.prepareRows(); + this.prepareTreeRows(); this.prepareRowView(); this.prepareNumericColumns(); @@ -994,13 +1014,25 @@ class DataManager { } } - meta.indent = d.indent; + meta.indent = d.indent || 0; } return this.prepareRow(row, meta); }); } + prepareTreeRows() { + this.rows.forEach((row, i) => { + if (isNumber(row.meta.indent)) { + // if (i === 36) debugger; + const nextRow = this.getRow(i + 1); + row.meta.isLeaf = !nextRow || + notSet(nextRow.meta.indent) || + nextRow.meta.indent <= row.meta.indent; + } + }); + } + prepareRowView() { // This is order in which rows will be rendered in the table. // When sorting happens, only this.rowViewOrder will change @@ -1322,21 +1354,44 @@ class DataManager { return this.getRow(rowIndex)[colIndex]; } - getChildrenIndices(parentRowIndex) { + getChildren(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; + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent)) continue; - while (nextIndent > parentIndent) { - out.push(i); + if (row.meta.indent > parentIndent) { + out.push(i); + } - i++; - nextRow = this.getRow(i); - nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + getImmediateChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + const childIndent = parentIndent + 1; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; + + if (row.meta.indent === childIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } } return out; @@ -1773,15 +1828,7 @@ class ColumnManager { } getFirstColumnIndex() { - if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) { - return 2; - } - - if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) { - return 1; - } - - return 0; + return this.datamanager.getColumnIndexById('_rowIndex') + 1; } getHeaderCell$(colIndex) { @@ -1814,14 +1861,16 @@ var getDropdownHTML = function getDropdownHTML(dropdownButton = 'v') { class CellManager { constructor(instance) { this.instance = instance; - this.wrapper = this.instance.wrapper; - this.options = this.instance.options; - this.style = this.instance.style; - this.bodyScrollable = this.instance.bodyScrollable; - this.columnmanager = this.instance.columnmanager; - this.rowmanager = this.instance.rowmanager; - this.datamanager = this.instance.datamanager; - this.keyboard = this.instance.keyboard; + linkProperties(this, this.instance, [ + 'wrapper', + 'options', + 'style', + 'bodyScrollable', + 'columnmanager', + 'rowmanager', + 'datamanager', + 'keyboard' + ]); this.bindEvents(); } @@ -1985,30 +2034,11 @@ class CellManager { const { rowIndex } = $.data($cell); if ($cell.classList.contains('tree-close')) { - this.rowmanager.openTreeNode(rowIndex); - $cell.classList.remove('tree-close'); + this.rowmanager.openSingleNode(rowIndex); } else { - this.rowmanager.closeTreeNode(rowIndex); - $cell.classList.add('tree-close'); + this.rowmanager.closeSingleNode(rowIndex); } }); - - // 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, { @@ -2361,17 +2391,25 @@ class CellManager { } refreshCell(cell) { - const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable); + const $cell = $(this.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); $cell.innerHTML = this.getCellContent(cell); } + toggleTreeButton(rowIndex, flag) { + const colIndex = this.columnmanager.getFirstColumnIndex(); + const $cell = this.getCell$(colIndex, rowIndex); + if ($cell) { + $cell.classList[flag ? 'remove' : 'add']('tree-close'); + } + } + isStandardCell(colIndex) { // Standard cells are in Sr. No and Checkbox column return colIndex < this.columnmanager.getFirstColumnIndex(); } getCell$(colIndex, rowIndex) { - return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable); + return $(this.selector(colIndex, rowIndex), this.bodyScrollable); } getAboveCell$($cell) { @@ -2519,7 +2557,7 @@ class CellManager { `; } - cellSelector(colIndex, rowIndex) { + selector(colIndex, rowIndex) { return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } @@ -2527,9 +2565,11 @@ class CellManager { class RowManager { constructor(instance) { this.instance = instance; - this.options = this.instance.options; - this.wrapper = this.instance.wrapper; - this.bodyScrollable = this.instance.bodyScrollable; + linkProperties(this, this.instance, [ + 'options', + 'wrapper', + 'bodyScrollable' + ]); this.bindEvents(); this.refreshRows = promisify(this.refreshRows, this); @@ -2675,6 +2715,7 @@ class RowManager { } hideRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.add('hide'); @@ -2682,20 +2723,33 @@ class RowManager { } showRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.remove('hide'); }); } - openTreeNode(rowIndex) { - const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + openSingleNode(rowIndex) { + const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); this.showRows(rowsToShow); + this.cellmanager.toggleTreeButton(rowIndex, true); } - closeTreeNode(rowIndex) { - const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); - this.hideRows(rowsToHide); + closeSingleNode(rowIndex) { + const children = this.datamanager.getImmediateChildren(rowIndex); + children.forEach(childIndex => { + const row = this.datamanager.getRow(childIndex); + if (row.meta.isLeaf) { + // close + this.hideRows(childIndex); + this.cellmanager.toggleTreeButton(childIndex, false); + } else { + this.closeSingleNode(childIndex); + this.hideRows(childIndex); + } + }); + this.cellmanager.toggleTreeButton(rowIndex, false); } getRow$(rowIndex) { diff --git a/src/cellmanager.js b/src/cellmanager.js index b7372ab..5b7a59c 100644 --- a/src/cellmanager.js +++ b/src/cellmanager.js @@ -1,7 +1,8 @@ import { copyTextToClipboard, makeDataAttributeString, - throttle + throttle, + linkProperties } from './utils'; import $ from './dom'; import { @@ -11,14 +12,16 @@ import { export default class CellManager { constructor(instance) { this.instance = instance; - this.wrapper = this.instance.wrapper; - this.options = this.instance.options; - this.style = this.instance.style; - this.bodyScrollable = this.instance.bodyScrollable; - this.columnmanager = this.instance.columnmanager; - this.rowmanager = this.instance.rowmanager; - this.datamanager = this.instance.datamanager; - this.keyboard = this.instance.keyboard; + linkProperties(this, this.instance, [ + 'wrapper', + 'options', + 'style', + 'bodyScrollable', + 'columnmanager', + 'rowmanager', + 'datamanager', + 'keyboard' + ]); this.bindEvents(); } @@ -182,30 +185,11 @@ export default class CellManager { const { rowIndex } = $.data($cell); if ($cell.classList.contains('tree-close')) { - this.rowmanager.openTreeNode(rowIndex); - $cell.classList.remove('tree-close'); + this.rowmanager.openSingleNode(rowIndex); } else { - this.rowmanager.closeTreeNode(rowIndex); - $cell.classList.add('tree-close'); + this.rowmanager.closeSingleNode(rowIndex); } }); - - // 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, { @@ -558,17 +542,25 @@ export default class CellManager { } refreshCell(cell) { - const $cell = $(this.cellSelector(cell.colIndex, cell.rowIndex), this.bodyScrollable); + const $cell = $(this.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); $cell.innerHTML = this.getCellContent(cell); } + toggleTreeButton(rowIndex, flag) { + const colIndex = this.columnmanager.getFirstColumnIndex(); + const $cell = this.getCell$(colIndex, rowIndex); + if ($cell) { + $cell.classList[flag ? 'remove' : 'add']('tree-close'); + } + } + isStandardCell(colIndex) { // Standard cells are in Sr. No and Checkbox column return colIndex < this.columnmanager.getFirstColumnIndex(); } getCell$(colIndex, rowIndex) { - return $(this.cellSelector(colIndex, rowIndex), this.bodyScrollable); + return $(this.selector(colIndex, rowIndex), this.bodyScrollable); } getAboveCell$($cell) { @@ -716,7 +708,7 @@ export default class CellManager { `; } - cellSelector(colIndex, rowIndex) { + selector(colIndex, rowIndex) { return `.data-table-cell[data-col-index="${colIndex}"][data-row-index="${rowIndex}"]`; } } diff --git a/src/columnmanager.js b/src/columnmanager.js index da71147..0cef7cf 100644 --- a/src/columnmanager.js +++ b/src/columnmanager.js @@ -406,15 +406,7 @@ export default class ColumnManager { } getFirstColumnIndex() { - if (this.options.addCheckboxColumn && this.options.addSerialNoColumn) { - return 2; - } - - if (this.options.addCheckboxColumn || this.options.addSerialNoColumn) { - return 1; - } - - return 0; + return this.datamanager.getColumnIndexById('_rowIndex') + 1; } getHeaderCell$(colIndex) { diff --git a/src/datamanager.js b/src/datamanager.js index 2bbb969..742503a 100644 --- a/src/datamanager.js +++ b/src/datamanager.js @@ -1,6 +1,8 @@ import { isNumeric, - promisify + promisify, + isNumber, + notSet } from './utils'; export default class DataManager { @@ -25,6 +27,7 @@ export default class DataManager { this.prepareColumns(); this.prepareRows(); + this.prepareTreeRows(); this.prepareRowView(); this.prepareNumericColumns(); @@ -173,13 +176,25 @@ export default class DataManager { } } - meta.indent = d.indent; + meta.indent = d.indent || 0; } return this.prepareRow(row, meta); }); } + prepareTreeRows() { + this.rows.forEach((row, i) => { + if (isNumber(row.meta.indent)) { + // if (i === 36) debugger; + const nextRow = this.getRow(i + 1); + row.meta.isLeaf = !nextRow || + notSet(nextRow.meta.indent) || + nextRow.meta.indent <= row.meta.indent; + } + }); + } + prepareRowView() { // This is order in which rows will be rendered in the table. // When sorting happens, only this.rowViewOrder will change @@ -501,21 +516,44 @@ export default class DataManager { return this.getRow(rowIndex)[colIndex]; } - getChildrenIndices(parentRowIndex) { + getChildren(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; + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent)) continue; - while (nextIndent > parentIndent) { - out.push(i); + if (row.meta.indent > parentIndent) { + out.push(i); + } - i++; - nextRow = this.getRow(i); - nextIndent = nextRow ? (nextRow.meta.indent || 0) : -1; + if (row.meta.indent === parentIndent) { + break; + } + } + + return out; + } + + getImmediateChildren(parentRowIndex) { + parentRowIndex = +parentRowIndex; + const parentIndent = this.getRow(parentRowIndex).meta.indent; + const out = []; + const childIndent = parentIndent + 1; + + for (let i = parentRowIndex + 1; i < this.rowCount; i++) { + const row = this.getRow(i); + if (isNaN(row.meta.indent) || row.meta.indent > childIndent) continue; + + if (row.meta.indent === childIndent) { + out.push(i); + } + + if (row.meta.indent === parentIndent) { + break; + } } return out; diff --git a/src/rowmanager.js b/src/rowmanager.js index 99b624e..86d3367 100644 --- a/src/rowmanager.js +++ b/src/rowmanager.js @@ -1,15 +1,19 @@ import $ from './dom'; import { makeDataAttributeString, - promisify + promisify, + ensureArray, + linkProperties } from './utils'; export default class RowManager { constructor(instance) { this.instance = instance; - this.options = this.instance.options; - this.wrapper = this.instance.wrapper; - this.bodyScrollable = this.instance.bodyScrollable; + linkProperties(this, this.instance, [ + 'options', + 'wrapper', + 'bodyScrollable' + ]); this.bindEvents(); this.refreshRows = promisify(this.refreshRows, this); @@ -155,6 +159,7 @@ export default class RowManager { } hideRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.add('hide'); @@ -162,20 +167,33 @@ export default class RowManager { } showRows(rowIndices) { + rowIndices = ensureArray(rowIndices); rowIndices.map(rowIndex => { const $tr = this.getRow$(rowIndex); $tr.classList.remove('hide'); }); } - openTreeNode(rowIndex) { - const rowsToShow = this.datamanager.getChildrenIndices(rowIndex); + openSingleNode(rowIndex) { + const rowsToShow = this.datamanager.getImmediateChildren(rowIndex); this.showRows(rowsToShow); + this.cellmanager.toggleTreeButton(rowIndex, true); } - closeTreeNode(rowIndex) { - const rowsToHide = this.datamanager.getChildrenIndices(rowIndex); - this.hideRows(rowsToHide); + closeSingleNode(rowIndex) { + const children = this.datamanager.getImmediateChildren(rowIndex); + children.forEach(childIndex => { + const row = this.datamanager.getRow(childIndex); + if (row.meta.isLeaf) { + // close + this.hideRows(childIndex); + this.cellmanager.toggleTreeButton(childIndex, false); + } else { + this.closeSingleNode(childIndex); + this.hideRows(childIndex); + } + }); + this.cellmanager.toggleTreeButton(rowIndex, false); } getRow$(rowIndex) { diff --git a/src/utils.js b/src/utils.js index e2969a1..a73566f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -183,3 +183,22 @@ export function linkProperties(target, source, properties) { }, {}); Object.defineProperties(target, props); }; + +export function isSet(val) { + return val !== undefined || val !== null; +} + +export function notSet(val) { + return !isSet(val); +} + +export function isNumber(val) { + return !isNaN(val); +} + +export function ensureArray(val) { + if (!Array.isArray(val)) { + return [val]; + } + return val; +}