Skip to content

Commit 2f91666

Browse files
authored
Merge pull request #229 from iamejaaz/3-attempt-sticky-columns
feat: sticky columns from UI
2 parents d2a4938 + 646946a commit 2f91666

File tree

9 files changed

+118
-39
lines changed

9 files changed

+118
-39
lines changed

cypress/integration/column.js

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
describe('Column', function () {
2-
before(function () {
2+
beforeEach(function () {
33
cy.visit('/');
44
});
55

@@ -68,48 +68,62 @@ describe('Column', function () {
6868
.and('match', /9\dpx/);
6969
});
7070

71-
it('keeps sticky columns pinned while scrolling horizontally', function () {
72-
const expectPinned = (actual, expected) => {
73-
expect(actual).to.be.closeTo(expected, 1);
74-
};
71+
it('pins a column from the dropdown menu', function () {
72+
cy.clickDropdown(2);
73+
cy.clickDropdownItem(2, 'Stick to left');
74+
75+
cy.window().then(win => win.datatable.getColumn(2))
76+
.its('sticky')
77+
.should('eq', true);
78+
79+
cy.get('.dt-scrollable').then(($scrollable) => {
80+
const scrollable = $scrollable[0];
81+
const stickyBodyCell = Cypress.$('.dt-cell--2-0')[0];
82+
const initialStickyBodyLeft = stickyBodyCell.getBoundingClientRect().left;
83+
84+
scrollable.scrollLeft = 220;
85+
scrollable.dispatchEvent(new Event('scroll'));
86+
87+
cy.wait(50).then(() => {
88+
const nextStickyBodyLeft = stickyBodyCell.getBoundingClientRect().left;
89+
expect(nextStickyBodyLeft).to.be.closeTo(initialStickyBodyLeft, 1);
90+
});
91+
});
92+
});
7593

94+
it('keeps sticky columns pinned while scrolling horizontally', function () {
7695
cy.get('.dt-scrollable').then(($scrollable) => {
7796
const scrollable = $scrollable[0];
78-
const stickyCheckboxBodyCell = Cypress.$('.dt-cell--0-0')[0];
79-
const stickyCheckboxHeaderCell = Cypress.$('.dt-cell--header-0')[0];
80-
const stickySerialBodyCell = Cypress.$('.dt-cell--1-0')[0];
81-
const stickySerialHeaderCell = Cypress.$('.dt-cell--header-1')[0];
82-
const stickyCustomBodyCell = Cypress.$('.dt-cell--2-0')[0];
83-
const stickyCustomHeaderCell = Cypress.$('.dt-cell--header-2')[0];
84-
const regularBodyCell = Cypress.$('.dt-cell--4-0')[0];
85-
86-
const initialStickyCheckboxBodyLeft = stickyCheckboxBodyCell.getBoundingClientRect().left;
87-
const initialStickyCheckboxHeaderLeft = stickyCheckboxHeaderCell.getBoundingClientRect().left;
88-
const initialStickySerialBodyLeft = stickySerialBodyCell.getBoundingClientRect().left;
89-
const initialStickySerialHeaderLeft = stickySerialHeaderCell.getBoundingClientRect().left;
90-
const initialStickyCustomBodyLeft = stickyCustomBodyCell.getBoundingClientRect().left;
91-
const initialStickyCustomHeaderLeft = stickyCustomHeaderCell.getBoundingClientRect().left;
92-
const initialRegularBodyLeft = regularBodyCell.getBoundingClientRect().left;
97+
const checkboxBodyCell = Cypress.$('.dt-cell--0-0')[0];
98+
const checkboxHeaderCell = Cypress.$('.dt-cell--header-0')[0];
99+
const serialBodyCell = Cypress.$('.dt-cell--1-0')[0];
100+
const serialHeaderCell = Cypress.$('.dt-cell--header-1')[0];
101+
const officeBodyCell = Cypress.$('.dt-cell--4-0')[0];
102+
const officeHeaderCell = Cypress.$('.dt-cell--header-4')[0];
103+
const nameBodyCell = Cypress.$('.dt-cell--2-0')[0];
104+
105+
const initialCheckboxLeft = checkboxBodyCell.getBoundingClientRect().left;
106+
const initialSerialLeft = serialBodyCell.getBoundingClientRect().left;
107+
const initialNameLeft = nameBodyCell.getBoundingClientRect().left;
93108

94109
scrollable.scrollLeft = 220;
95110
scrollable.dispatchEvent(new Event('scroll'));
96111

97112
cy.wait(50).then(() => {
98-
const nextStickyCheckboxBodyLeft = stickyCheckboxBodyCell.getBoundingClientRect().left;
99-
const nextStickyCheckboxHeaderLeft = stickyCheckboxHeaderCell.getBoundingClientRect().left;
100-
const nextStickySerialBodyLeft = stickySerialBodyCell.getBoundingClientRect().left;
101-
const nextStickySerialHeaderLeft = stickySerialHeaderCell.getBoundingClientRect().left;
102-
const nextStickyCustomBodyLeft = stickyCustomBodyCell.getBoundingClientRect().left;
103-
const nextStickyCustomHeaderLeft = stickyCustomHeaderCell.getBoundingClientRect().left;
104-
const nextRegularBodyLeft = regularBodyCell.getBoundingClientRect().left;
105-
106-
expectPinned(nextStickyCheckboxBodyLeft, initialStickyCheckboxBodyLeft);
107-
expectPinned(nextStickyCheckboxHeaderLeft, initialStickyCheckboxHeaderLeft);
108-
expectPinned(nextStickySerialBodyLeft, initialStickySerialBodyLeft);
109-
expectPinned(nextStickySerialHeaderLeft, initialStickySerialHeaderLeft);
110-
expectPinned(nextStickyCustomBodyLeft, initialStickyCustomBodyLeft);
111-
expectPinned(nextStickyCustomHeaderLeft, initialStickyCustomHeaderLeft);
112-
expect(nextRegularBodyLeft).to.be.lessThan(initialRegularBodyLeft);
113+
const nextCheckboxBodyLeft = checkboxBodyCell.getBoundingClientRect().left;
114+
const nextCheckboxHeaderLeft = checkboxHeaderCell.getBoundingClientRect().left;
115+
const nextSerialBodyLeft = serialBodyCell.getBoundingClientRect().left;
116+
const nextSerialHeaderLeft = serialHeaderCell.getBoundingClientRect().left;
117+
const nextOfficeBodyLeft = officeBodyCell.getBoundingClientRect().left;
118+
const nextOfficeHeaderLeft = officeHeaderCell.getBoundingClientRect().left;
119+
const nextNameLeft = nameBodyCell.getBoundingClientRect().left;
120+
121+
expect(nextCheckboxBodyLeft).to.be.closeTo(initialCheckboxLeft, 1);
122+
expect(nextSerialBodyLeft).to.be.closeTo(initialSerialLeft, 1);
123+
expect(nextCheckboxHeaderLeft).to.be.closeTo(nextCheckboxBodyLeft, 1);
124+
expect(nextSerialHeaderLeft).to.be.closeTo(nextSerialBodyLeft, 1);
125+
expect(nextOfficeHeaderLeft).to.be.closeTo(nextOfficeBodyLeft, 1);
126+
expect(nextNameLeft).to.be.lessThan(initialNameLeft);
113127
});
114128
});
115129
});

index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ <h1>Frappe DataTable</h1>
151151

152152
function buildData() {
153153
columns = [
154-
{ name: "Name", width: 150, sticky: true },
155-
{ name: "Position", width: 200 },
156-
{ name: "Office", sticky: true },
154+
{ name: "Name", width: 150},
155+
{ name: "Position", width: 200},
156+
{ name: "Office", sticky: true},
157157
{ name: "Extn." },
158158
{
159159
name: "Start Date",

src/columnmanager.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export default class ColumnManager {
9393
});
9494

9595
$.on(this.$dropdownList, 'click', '.dt-dropdown__list-item', (e, $item) => {
96-
if (!this._dropdownActiveColIndex) return;
96+
if (this._dropdownActiveColIndex == null) return;
9797
const dropdownItems = this.options.headerDropdown;
9898
const { index } = $.data($item);
9999
const colIndex = this._dropdownActiveColIndex;
@@ -108,6 +108,11 @@ export default class ColumnManager {
108108
_this.hideDropdown();
109109
}
110110

111+
this.stickDropdownIndex = this.options.headerDropdown
112+
.findIndex(item => item.stickyAction === 'stick');
113+
this.unstickDropdownIndex = this.options.headerDropdown
114+
.findIndex(item => item.stickyAction === 'unstick');
115+
111116
this.hideDropdown();
112117
}
113118

@@ -124,6 +129,7 @@ export default class ColumnManager {
124129
const $cell = $.closest('.dt-cell', e.target);
125130
const { colIndex } = $.data($cell);
126131
this._dropdownActiveColIndex = colIndex;
132+
this.updateStickyDropdownItems(this.getColumn(colIndex));
127133
}
128134

129135
hideDropdown() {
@@ -304,6 +310,20 @@ export default class ColumnManager {
304310
});
305311
}
306312

313+
setColumnSticky(colIndex, sticky) {
314+
const column = this.getColumn(colIndex);
315+
if (!column || column.sticky === sticky) {
316+
return;
317+
}
318+
319+
this.instance.freeze();
320+
this.datamanager.updateColumn(colIndex, { sticky });
321+
322+
this.refreshHeader();
323+
this.rowmanager.refreshRows()
324+
.then(() => this.instance.unfreeze());
325+
}
326+
307327
switchColumn(oldIndex, newIndex) {
308328
this.instance.freeze();
309329
this.datamanager.switchColumn(oldIndex, newIndex)
@@ -493,4 +513,21 @@ export default class ColumnManager {
493513
toggleDropdownItem(index) {
494514
$('.dt-dropdown__list', this.instance.dropdownContainer).children[index].classList.toggle('dt-hidden');
495515
}
516+
517+
updateStickyDropdownItems(column) {
518+
if (!column) return;
519+
if (this.stickDropdownIndex === -1 || this.unstickDropdownIndex === -1) return;
520+
521+
const stickItem = this.$dropdownList.children[this.stickDropdownIndex];
522+
const unstickItem = this.$dropdownList.children[this.unstickDropdownIndex];
523+
if (!(stickItem && unstickItem)) return;
524+
525+
if (column.sticky) {
526+
stickItem.classList.add('dt-hidden');
527+
unstickItem.classList.remove('dt-hidden');
528+
} else {
529+
stickItem.classList.remove('dt-hidden');
530+
unstickItem.classList.add('dt-hidden');
531+
}
532+
}
496533
}

src/datatable.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ class DataTable {
222222
this.columnmanager.removeColumn(colIndex);
223223
}
224224

225+
setColumnSticky(colIndex, sticky) {
226+
this.columnmanager.setColumnSticky(colIndex, sticky);
227+
}
228+
225229
scrollToLastColumn() {
226230
this.datatableWrapper.scrollLeft = 9999;
227231
}

src/defaults.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ export default function getDefaultOptions(instance) {
3030
action: function (column) {
3131
this.removeColumn(column.colIndex);
3232
}
33+
},
34+
{
35+
label: instance.translate('Stick to left'),
36+
stickyAction: 'stick',
37+
display: 'hidden',
38+
action: function (column) {
39+
this.setColumnSticky(column.colIndex, true);
40+
}
41+
},
42+
{
43+
label: instance.translate('Unstick from left'),
44+
stickyAction: 'unstick',
45+
display: 'hidden',
46+
action: function (column) {
47+
this.setColumnSticky(column.colIndex, false);
48+
}
3349
}
3450
],
3551
events: {

src/translations/de.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"Sort Descending": "Absteigend sortieren",
44
"Reset sorting": "Sortierung zurücksetzen",
55
"Remove column": "Spalte entfernen",
6+
"Stick to left": "Links anheften",
7+
"Unstick from left": "Linke Anheftung lösen",
68
"No Data": "Keine Daten",
79
"{count} cells copied": {
810
"1": "{count} Zelle kopiert",

src/translations/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"Sort Descending": "Sort Descending",
44
"Reset sorting": "Reset sorting",
55
"Remove column": "Remove column",
6+
"Stick to left": "Stick to left",
7+
"Unstick from left": "Unstick from left",
68
"No Data": "No Data",
79
"{count} cells copied": {
810
"1": "{count} cell copied",

src/translations/fr.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"Sort Descending": "Trier par ordre décroissant",
44
"Reset sorting": "Réinitialiser le tri",
55
"Remove column": "Supprimer colonne",
6+
"Stick to left": "Épingler à gauche",
7+
"Unstick from left": "Désépingler de la gauche",
68
"No Data": "Pas de données",
79
"{count} cells copied": {
810
"1": "{count} cellule copiée",

src/translations/it.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"Sort Descending": "Ordinamento decrescente",
44
"Reset sorting": "Azzeramento ordinamento",
55
"Remove column": "Rimuovi colonna",
6+
"Stick to left": "Blocca a sinistra",
7+
"Unstick from left": "Sblocca dalla sinistra",
68
"No Data": "Nessun dato",
79
"{count} cells copied": {
810
"1": "Copiato {count} cella",

0 commit comments

Comments
 (0)