From fd24cfc4dac0c1c7f55698ddddf2c97a88a603ae Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Wed, 30 Apr 2025 00:19:10 +0530 Subject: [PATCH 1/4] refactor: revert sticky column change --- index.html | 4 ++-- src/cellmanager.js | 38 +------------------------------------- src/style.css | 13 ++----------- src/style.js | 42 +++++------------------------------------- 4 files changed, 10 insertions(+), 87 deletions(-) diff --git a/index.html b/index.html index bd545dd..58d69e3 100644 --- a/index.html +++ b/index.html @@ -151,9 +151,9 @@

Frappe DataTable

function buildData() { columns = [ - { name: "Name", width: 150, sticky: true }, + { name: "Name", width: 150 }, { name: "Position", width: 200 }, - { name: "Office", sticky: true }, + { name: "Office" }, { name: "Extn." }, { name: "Start Date", diff --git a/src/cellmanager.js b/src/cellmanager.js index bf31217..90da047 100644 --- a/src/cellmanager.js +++ b/src/cellmanager.js @@ -802,44 +802,10 @@ export default class CellManager { isTotalRow }); - let styles = ''; - const row = this.datamanager.getRow(rowIndex); const isBodyCell = !(isHeader || isFilter || isTotalRow); - const serialNoColIndex = !this.options.checkboxColumn && this.options.serialNoColumn ? 0 : 1; - - let sticky = false; - - let checkboxserialNoclass = ''; - - if (colIndex === 0 && this.options.checkboxColumn) { - if (cell.isHeader && !(cell.id in this.stickyColWitdh)) this.stickyRowWidth = 34; - checkboxserialNoclass = 'dt-cell-checkbox'; - sticky = true; - } else if (colIndex === serialNoColIndex && this.options.serialNoColumn) { - if (cell.isHeader && !(cell.id in this.stickyColWitdh)) { - this.stickyColWitdh[cell.id] = this.stickyRowWidth; - this.stickyRowWidth += (cell.width || 37); - checkboxserialNoclass = 'dt-cell-serial-no'; - } - styles = `left:${this.stickyColWitdh[isBodyCell ? cell.column.id : cell.id]}px;`; - sticky = true; - - } else if (cell.sticky) { - if (cell.isHeader && !(cell.id in this.stickyColWitdh)) { - this.stickyColWitdh[cell.id] = this.stickyRowWidth; - this.stickyRowWidth += ((cell.width || 100) + 1); - } - styles = `left:${this.stickyColWitdh[cell.id]}px;`; - sticky = true; - - } else if ((isBodyCell || isTotalRow) && cell.column.sticky) { - styles = `left:${this.stickyColWitdh[cell.column.id]}px;`; - sticky = true; - } - const className = [ 'dt-cell', 'dt-cell--col-' + colIndex, @@ -849,12 +815,10 @@ export default class CellManager { isHeader ? `dt-cell--header-${colIndex}` : '', isFilter ? 'dt-cell--filter' : '', isBodyCell && (row && row.meta.isTreeNodeClose) ? 'dt-cell--tree-close' : '', - sticky ? 'dt-sticky-col' : '', - checkboxserialNoclass, ].join(' '); return ` -
+
${this.getCellContent(cell)}
`; diff --git a/src/style.css b/src/style.css index 451a151..8853253 100644 --- a/src/style.css +++ b/src/style.css @@ -36,14 +36,10 @@ border-bottom: 2px solid var(--dt-border-color); } -.datatable-content{ - .dt-header{ - display: flex; - } -} - .dt-scrollable { height: 40vw; + overflow: auto; + border-top: 2px solid var(--dt-border-color); &--highlight-all { background-color: var(--dt-selection-highlight-color); @@ -300,9 +296,4 @@ body.dt-resize { cursor: col-resize; -} -.dt-sticky-col { - position: sticky; - z-index: 1; - left: 0; } \ No newline at end of file diff --git a/src/style.js b/src/style.js index 051b75f..c87d9b8 100644 --- a/src/style.js +++ b/src/style.js @@ -43,51 +43,19 @@ export default class Style { this._settingHeaderPosition = false; $.on(this.bodyScrollable, 'scroll', (e) => { - if (this._settingHeaderPosition) return; this._settingHeaderPosition = true; requestAnimationFrame(() => { + const left = -e.target.scrollLeft; - const scrollLeft = e.target.scrollLeft; - - // Move non-sticky header and footer cells normally - const nonStickyHeaderCells = this.header.querySelectorAll('.dt-cell:not(.dt-sticky-col)'); - const nonStickyFooterCells = this.footer.querySelectorAll('.dt-cell:not(.dt-sticky-col)'); - - nonStickyHeaderCells.forEach(cell => { - $.style(cell, { transform: `translateX(${-scrollLeft}px)` }); + $.style(this.header, { + transform: `translateX(${left}px)` }); - - nonStickyFooterCells.forEach(cell => { - $.style(cell, { transform: `translateX(${-scrollLeft}px)` }); - }); - - const stickyHeaderCells = this.header.querySelectorAll( - '.dt-cell.dt-sticky-col:not(.dt-cell-serial-no):not(.dt-cell-checkbox)' - ); - - stickyHeaderCells.forEach((headerCell) => { - - const colIndex = headerCell.getAttribute('data-col-index'); - const bodyCell = this.bodyScrollable.querySelector(`.dt-cell[data-col-index="${colIndex}"]`); - const colLeft = parseFloat(headerCell.style.left) || 0; // get left position of the column - - // Find corresponding footer cell - const footerCell = this.footer.querySelector(`.dt-cell[data-col-index="${colIndex}"]`); - - if (~~(bodyCell.offsetLeft - scrollLeft) > colLeft) { - headerCell.style.transform = `translateX(${-scrollLeft - 1}px)`; - if (footerCell) { - footerCell.style.transform = `translateX(${scrollLeft ? -scrollLeft - 1 : 0}px)`; - } - } else { - headerCell.style.transform = `translateX(${colLeft - headerCell.offsetLeft}px)`; - if (footerCell) footerCell.style.transform = `translateX(${colLeft - footerCell.offsetLeft}px)`; - } + $.style(this.footer, { + transform: `translateX(${left}px)` }); - this._settingHeaderPosition = false; }); }); From fabe755a376ece9a93eedc7e0f1f947fd180e7c0 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Tue, 6 May 2025 12:50:30 +0530 Subject: [PATCH 2/4] refactor: remove sticky behaviour and change layout --- src/body-renderer.js | 28 ++++++++++++++++++++++------ src/cellmanager.js | 2 -- src/columnmanager.js | 6 ++++++ src/datatable.js | 13 ++++++++++--- src/defaults.js | 6 ++++++ src/style.css | 31 +++++++++++++++++++++++++++---- src/style.js | 23 ----------------------- src/translations/de.json | 1 + src/translations/en.json | 1 + src/translations/fr.json | 1 + src/translations/it.json | 1 + 11 files changed, 75 insertions(+), 38 deletions(-) diff --git a/src/body-renderer.js b/src/body-renderer.js index 1176e3e..df70f97 100644 --- a/src/body-renderer.js +++ b/src/body-renderer.js @@ -8,7 +8,9 @@ export default class BodyRenderer { this.rowmanager = instance.rowmanager; this.cellmanager = instance.cellmanager; this.bodyScrollable = instance.bodyScrollable; + this.bodyContainer = instance.bodyContainer; this.footer = this.instance.footer; + this.header = this.instance.header; this.log = instance.log; } @@ -32,11 +34,7 @@ export default class BodyRenderer { return null; }).filter(index => index !== null); - const computedStyle = getComputedStyle(this.bodyScrollable); - let config = { - width: computedStyle.width, - height: computedStyle.height, itemHeight: this.options.cellHeight, total: rows.length, generate: (index) => { @@ -53,9 +51,9 @@ export default class BodyRenderer { }; if (!this.hyperlist) { - this.hyperlist = new HyperList(this.bodyScrollable, config); + this.hyperlist = new HyperList(this.bodyContainer, config); } else { - this.hyperlist.refresh(this.bodyScrollable, config); + this.hyperlist.refresh(this.bodyContainer, config); } this.renderFooter(); @@ -123,6 +121,24 @@ export default class BodyRenderer { this.rowmanager.highlightCheckedRows(); this.cellmanager.selectAreaOnClusterChanged(); this.cellmanager.focusCellOnClusterChanged(); + this.bodyContainer.style.removeProperty('overflow'); + this.setbodyWidth(); + } + + setbodyWidth() { + // change width of dt-body once the all operations are done + setTimeout(() => { + const computedStyle = getComputedStyle(this.bodyScrollable); + + const cells = this.header.querySelectorAll('.dt-cell--header'); + let totalColWidth = 0; + cells.forEach((cell, index) => { + totalColWidth += cell.clientWidth + 1; + }); + this.bodyContainer.style.width = `${totalColWidth + 5}px`; + this.bodyContainer.style.height = `calc(${computedStyle.height} - 80px)`; + this.bodyScrollable.style.height = computedStyle.height; + }, 100); } showToastMessage(message, hideAfter) { diff --git a/src/cellmanager.js b/src/cellmanager.js index 90da047..1ace672 100644 --- a/src/cellmanager.js +++ b/src/cellmanager.js @@ -25,8 +25,6 @@ export default class CellManager { ]); this.bindEvents(); - this.stickyRowWidth = 0; - this.stickyColWitdh = []; } bindEvents() { diff --git a/src/columnmanager.js b/src/columnmanager.js index fabfbe7..8330994 100644 --- a/src/columnmanager.js +++ b/src/columnmanager.js @@ -166,6 +166,7 @@ export default class ColumnManager { this.setColumnWidth(colIndex); this.style.setBodyStyle(); $resizingCell = null; + this.bodyRenderer.setbodyWidth(); }; $.on(document.body, 'mouseup', onMouseup); this.instance.on('onDestroy', () => { @@ -270,6 +271,11 @@ export default class ColumnManager { }); } + pinColumn(colIndex) { + this.instance.freeze(); + this.instance.unfreeze(); + } + removeColumn(colIndex) { const removedCol = this.getColumn(colIndex); this.instance.freeze(); diff --git a/src/datatable.js b/src/datatable.js index b265511..314fa4d 100644 --- a/src/datatable.js +++ b/src/datatable.js @@ -110,9 +110,11 @@ class DataTable { this.wrapper.innerHTML = `
-
-
- +
+
+
+ +
@@ -127,6 +129,7 @@ class DataTable { this.datatableWrapper = $('.datatable', this.wrapper); this.header = $('.dt-header', this.wrapper); + this.bodyContainer = $('.dt-body', this.wrapper); this.footer = $('.dt-footer', this.wrapper); this.bodyScrollable = $('.dt-scrollable', this.wrapper); this.freezeContainer = $('.dt-freeze', this.wrapper); @@ -213,6 +216,10 @@ class DataTable { this.columnmanager.sortColumn(colIndex, sortOrder); } + pinColumn(colIndex) { + this.columnmanager.pinColumn(colIndex); + } + removeColumn(colIndex) { this.columnmanager.removeColumn(colIndex); } diff --git a/src/defaults.js b/src/defaults.js index 359f8e8..8c453e9 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -25,6 +25,12 @@ export default function getDefaultOptions(instance) { this.sortColumn(column.colIndex, 'none'); } }, + // { + // label: instance.translate('Pin column'), + // action: function (column) { + // this.pinColumn(column.colIndex); + // } + // }, { label: instance.translate('Remove column'), action: function (column) { diff --git a/src/style.css b/src/style.css index 8853253..eea7c67 100644 --- a/src/style.css +++ b/src/style.css @@ -32,14 +32,28 @@ } } -.dt-header{ - border-bottom: 2px solid var(--dt-border-color); +.dt-header { + top: 0; +} + +.dt-header, +.dt-footer { + position: sticky; + background: white; +} + +.dt-footer { + bottom: 0; +} +.dt-body { + overflow-y: auto; + overflow-x: clip; } .dt-scrollable { height: 40vw; - overflow: auto; border-top: 2px solid var(--dt-border-color); + overflow-y: hidden; &--highlight-all { background-color: var(--dt-selection-highlight-color); @@ -68,7 +82,7 @@ display: none; } - &:last-child:not(.dt-row-filter) { + &:last-child:not(.dt-row-filter) .dt-cell { border-bottom: 1px solid var(--dt-border-color); } } @@ -141,6 +155,7 @@ &--header { background-color: var(--dt-header-cell-bg); + border-bottom: 2px solid var(--dt-border-color); } &--header:last-child { @@ -296,4 +311,12 @@ body.dt-resize { cursor: col-resize; +} + +.dt-sticky-col { + /* position: sticky; */ + left: 0; + /* background: #fff; */ + z-index: 2; + /* border-right: 1px solid #ccc; */ } \ No newline at end of file diff --git a/src/style.js b/src/style.js index c87d9b8..a82681b 100644 --- a/src/style.js +++ b/src/style.js @@ -23,7 +23,6 @@ export default class Style { this.styleEl = styleEl; this.bindResizeWindow(); - this.bindScrollHeader(); } get stylesheet() { @@ -39,28 +38,6 @@ export default class Style { } } - bindScrollHeader() { - this._settingHeaderPosition = false; - - $.on(this.bodyScrollable, 'scroll', (e) => { - if (this._settingHeaderPosition) return; - - this._settingHeaderPosition = true; - - requestAnimationFrame(() => { - const left = -e.target.scrollLeft; - - $.style(this.header, { - transform: `translateX(${left}px)` - }); - $.style(this.footer, { - transform: `translateX(${left}px)` - }); - this._settingHeaderPosition = false; - }); - }); - } - onWindowResize() { this.distributeRemainingWidth(); this.refreshColumnWidth(); diff --git a/src/translations/de.json b/src/translations/de.json index 0e667df..1a74b4c 100644 --- a/src/translations/de.json +++ b/src/translations/de.json @@ -2,6 +2,7 @@ "Sort Ascending": "Aufsteigend sortieren", "Sort Descending": "Absteigend sortieren", "Reset sorting": "Sortierung zurücksetzen", + "Pin column": "Spalte anheften", "Remove column": "Spalte entfernen", "No Data": "Keine Daten", "{count} cells copied": { diff --git a/src/translations/en.json b/src/translations/en.json index 8029868..16ac80a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2,6 +2,7 @@ "Sort Ascending": "Sort Ascending", "Sort Descending": "Sort Descending", "Reset sorting": "Reset sorting", + "Pin column": "Pin column", "Remove column": "Remove column", "No Data": "No Data", "{count} cells copied": { diff --git a/src/translations/fr.json b/src/translations/fr.json index 194ec10..80bd0f1 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -2,6 +2,7 @@ "Sort Ascending": "Trier par ordre croissant", "Sort Descending": "Trier par ordre décroissant", "Reset sorting": "Réinitialiser le tri", + "Pin column": "Épingler la colonne", "Remove column": "Supprimer colonne", "No Data": "Pas de données", "{count} cells copied": { diff --git a/src/translations/it.json b/src/translations/it.json index a7308c1..1f19b41 100644 --- a/src/translations/it.json +++ b/src/translations/it.json @@ -2,6 +2,7 @@ "Sort Ascending": "Ordinamento ascendente", "Sort Descending": "Ordinamento decrescente", "Reset sorting": "Azzeramento ordinamento", + "Pin column": "Colonna perno", "Remove column": "Rimuovi colonna", "No Data": "Nessun dato", "{count} cells copied": { From 8506e5b53d594761f1c1bf1c885e581f1aa0ad08 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Tue, 6 May 2025 12:58:55 +0530 Subject: [PATCH 3/4] chore: remove not used style --- src/style.css | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/style.css b/src/style.css index 14c4be9..e931924 100644 --- a/src/style.css +++ b/src/style.css @@ -320,12 +320,4 @@ body.dt-resize { cursor: col-resize; -} - -.dt-sticky-col { - /* position: sticky; */ - left: 0; - /* background: #fff; */ - z-index: 2; - /* border-right: 1px solid #ccc; */ } \ No newline at end of file From 4a16e4283ab64e8d24b9b2f5b75a2321ad477ec9 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Tue, 6 May 2025 15:31:59 +0530 Subject: [PATCH 4/4] test: fix failing test cases --- cypress/integration/column.js | 6 +++--- cypress/integration/inline_filters.js | 2 +- cypress/integration/row.js | 2 +- src/cellmanager.js | 20 +++++++++++--------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cypress/integration/column.js b/cypress/integration/column.js index 3467e8e..c9a3bbd 100644 --- a/cypress/integration/column.js +++ b/cypress/integration/column.js @@ -29,7 +29,7 @@ describe('Column', function () { .its('currentSort.colIndex') .should('eq', 2); - cy.get('.dt-scrollable .dt-row:first div:nth-of-type(3)') + cy.get('.dt-scrollable .dt-body .dt-row:first div:nth-of-type(3)') .contains('Airi Satou'); cy.clickDropdownItem(2, 'Reset sorting'); @@ -63,8 +63,8 @@ describe('Column', function () { it('resize column using double click', function () { cy.get('.dt-cell--header-4 .dt-cell__resize-handle').trigger('dblclick'); cy.getColumnCell(4).should('have.css', 'width') - .and('match', /9\dpx/); + .and('match', /10\dpx/); cy.getCell(4, 1).should('have.css', 'width') - .and('match', /9\dpx/); + .and('match', /10\dpx/); }); }); diff --git a/cypress/integration/inline_filters.js b/cypress/integration/inline_filters.js index c18e94a..8964a55 100644 --- a/cypress/integration/inline_filters.js +++ b/cypress/integration/inline_filters.js @@ -81,7 +81,7 @@ describe('Inline Filters', function () { cy.getCell(5, 24).click().type('{ctrl}f'); cy.get('@filterInput5').type('>3000', {delay: 100}); - cy.get('.dt-scrollable .dt-row:first') + cy.get('.dt-body .dt-row:first') .should('contain', 'Angelica') .should('have.class', 'dt-row-24'); cy.get('@filterInput5').clear(); diff --git a/cypress/integration/row.js b/cypress/integration/row.js index 1278e79..9cd7fcd 100644 --- a/cypress/integration/row.js +++ b/cypress/integration/row.js @@ -4,7 +4,7 @@ describe('Row', function () { }); it('check / uncheck row', function () { - cy.get('.dt-scrollable .dt-row:first') + cy.get('.dt-body .dt-row:first') .find('input[type="checkbox"]') .click(); diff --git a/src/cellmanager.js b/src/cellmanager.js index 1ace672..ea3d8af 100644 --- a/src/cellmanager.js +++ b/src/cellmanager.js @@ -17,6 +17,7 @@ export default class CellManager { 'style', 'header', 'bodyScrollable', + 'bodyContainer', 'columnmanager', 'rowmanager', 'datamanager', @@ -43,7 +44,7 @@ export default class CellManager { bindEditCell() { this.$editingCell = null; - $.on(this.bodyScrollable, 'dblclick', '.dt-cell', (e, cell) => { + $.on(this.bodyContainer, 'dblclick', '.dt-cell', (e, cell) => { this.activateEditing(cell); }); @@ -100,6 +101,7 @@ export default class CellManager { const $cell = $.closest('.dt-cell', e.target); const { colIndex } = $.data($cell); + console.log(colIndex, "colIndex", colIndex); this.activateFilter(colIndex); return true; }); @@ -164,12 +166,12 @@ export default class CellManager { bindMouseEvents() { let mouseDown = null; - $.on(this.bodyScrollable, 'mousedown', '.dt-cell', (e) => { + $.on(this.bodyContainer, 'mousedown', '.dt-cell', (e) => { mouseDown = true; this.focusCell($(e.delegatedTarget)); }); - $.on(this.bodyScrollable, 'mouseup', () => { + $.on(this.bodyContainer, 'mouseup', () => { mouseDown = false; }); @@ -186,11 +188,11 @@ export default class CellManager { this.selectArea($(e.delegatedTarget)); }; - $.on(this.bodyScrollable, 'mousemove', '.dt-cell', throttle(selectArea, 50)); + $.on(this.bodyContainer, 'mousemove', '.dt-cell', throttle(selectArea, 50)); } bindTreeEvents() { - $.on(this.bodyScrollable, 'click', '.dt-tree-node__toggle', (e, $toggle) => { + $.on(this.bodyContainer, 'click', '.dt-tree-node__toggle', (e, $toggle) => { const $cell = $.closest('.dt-cell', $toggle); const { rowIndex } = $.data($cell); @@ -646,7 +648,7 @@ export default class CellManager { } refreshCell(cell, refreshHtml = false) { - const $cell = $(this.selector(cell.colIndex, cell.rowIndex), this.bodyScrollable); + const $cell = $(this.selector(cell.colIndex, cell.rowIndex), this.bodyContainer); $cell.innerHTML = this.getCellContent(cell, refreshHtml); } @@ -707,7 +709,7 @@ export default class CellManager { } getCell$(colIndex, rowIndex) { - return $(this.selector(colIndex, rowIndex), this.bodyScrollable); + return $(this.selector(colIndex, rowIndex), this.bodyContainer); } getAboveCell$($cell) { @@ -767,11 +769,11 @@ export default class CellManager { } getRowHeight() { - return $.style($('.dt-row', this.bodyScrollable), 'height'); + return $.style($('.dt-row', this.bodyContainer), 'height'); } scrollToCell($cell) { - if ($.inViewport($cell, this.bodyScrollable) || $.inViewport($cell, this.footer)) return false; + if ($.inViewport($cell, this.bodyContainer) || $.inViewport($cell, this.footer)) return false; const { rowIndex