Skip to content

Commit 98776f3

Browse files
authored
[PBNTR-768] Adding sticky right column and refactoring PBTable enhanced element (#4135)
**What does this PR do?** Adding sticky right column and refactoring PBTable enhanced element. **What was done:** 1. Using the class selector (".table-responsive-collapse"), we weren't running the PBTable Enhanced Element for all the tables. What made this work for the other tables was the "document.querySelectorAll". 2. Using "document.querySelectorAll" caused unexpected behaviors (as mentioned above) and performance issues. For each table, we would run the same logic for ALL the tables on the page. This is an On2 code. If we had 10 tables on the page, we would have run it 100 times. 3. I removed the use of ref functions and binds since using arrow functions, we don't need it. 4. The React version was executing PBTable Enhanced Element causing all the issues mentioned above. This code was added 5 years ago so we could add the data atttribute "data-title" to cells. I removed this connection and I am proposing on "Next steps" we refactor the way we do it. **How to test?** Steps to confirm the desired behavior: 1. Go to Table doc page for Rails and React #### Checklist: - [x] **LABELS** Add a label: `enhancement`, `bug`, `improvement`, `new kit`, `deprecated`, or `breaking`. See [Changelog & Labels](https://github.com/powerhome/playbook/wiki/Changelog-&-Labels) for details. - [x] **DEPLOY** I have added the `milano` label to show I'm ready for a review. - [ ] **TESTS** I have added test coverage to my code.
1 parent eccf8a9 commit 98776f3

File tree

12 files changed

+294
-43
lines changed

12 files changed

+294
-43
lines changed

playbook/app/pb_kits/playbook/pb_table/_table.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import React, { useEffect } from 'react'
22
import classnames from 'classnames'
33
import { buildAriaProps, buildDataProps, buildHtmlProps } from '../utilities/props'
44
import { globalProps, GlobalProps, globalInlineProps } from '../utilities/globalProps'
5-
import PbTable from '.'
65
import {
76
TableHead,
87
TableHeader,
98
TableBody,
109
TableRow,
1110
TableCell,
1211
} from "./subcomponents";
12+
import { addDataTitle } from './utilities/addDataTitle'
1313

1414
type TableProps = {
1515
aria?: { [key: string]: string },
@@ -196,8 +196,7 @@ const Table = (props: TableProps): React.ReactElement => {
196196
}, [stickyRightColumn]);
197197

198198
useEffect(() => {
199-
const instance = new PbTable()
200-
instance.connect()
199+
addDataTitle()
201200
}, [])
202201

203202
return (
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<%= pb_rails("table", props: { size: "md", responsive: "scroll", sticky_left_column: ["a"], sticky_right_column: ["b"] }) do %>
2+
<thead>
3+
<tr>
4+
<th id="a">Column 1</th>
5+
<th>Column 2</th>
6+
<th>Column 3</th>
7+
<th>Column 4</th>
8+
<th>Column 5</th>
9+
<th>Column 6</th>
10+
<th>Column 7</th>
11+
<th>Column 8</th>
12+
<th>Column 9</th>
13+
<th>Column 10</th>
14+
<th>Column 11</th>
15+
<th>Column 12</th>
16+
<th>Column 13</th>
17+
<th>Column 14</th>
18+
<th id="b">Column 15</th>
19+
</tr>
20+
</thead>
21+
<tbody>
22+
<tr>
23+
<td id="a">Value 1</td>
24+
<td>Value 2</td>
25+
<td>Value 3</td>
26+
<td>Value 4</td>
27+
<td>Value 5</td>
28+
<td>Value 6</td>
29+
<td>Value 7</td>
30+
<td>Value 8</td>
31+
<td>Value 9</td>
32+
<td>Value 10</td>
33+
<td>Value 11</td>
34+
<td>Value 12</td>
35+
<td>Value 13</td>
36+
<td>Value 14</td>
37+
<td id="b">Value 15</td>
38+
</tr>
39+
<tr>
40+
<td id="a">Value 1</td>
41+
<td>Value 2</td>
42+
<td>Value 3</td>
43+
<td>Value 4</td>
44+
<td>Value 5</td>
45+
<td>Value 6</td>
46+
<td>Value 7</td>
47+
<td>Value 8</td>
48+
<td>Value 9</td>
49+
<td>Value 10</td>
50+
<td>Value 11</td>
51+
<td>Value 12</td>
52+
<td>Value 13</td>
53+
<td>Value 14</td>
54+
<td id="b">Value 15</td>
55+
</tr>
56+
<tr>
57+
<td id="a">Value 1</td>
58+
<td>Value 2</td>
59+
<td>Value 3</td>
60+
<td>Value 4</td>
61+
<td>Value 5</td>
62+
<td>Value 6</td>
63+
<td>Value 7</td>
64+
<td>Value 8</td>
65+
<td>Value 9</td>
66+
<td>Value 10</td>
67+
<td>Value 11</td>
68+
<td>Value 12</td>
69+
<td>Value 13</td>
70+
<td>Value 14</td>
71+
<td id="b">Value 15</td>
72+
</tr>
73+
</tbody>
74+
<% end %>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The `sticky_left_column` and `sticky_right_column` props can be used together on the same table as needed.
2+
3+
Please ensure that unique ids are used for all columns across multiple tables. Using the same columns ids on multiple tables can lead to issues when using props.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
The `stickyLeftColumn` prop expects an array of the column ids you want to be sticky. Make sure to add the corresponding id to the `<th>` and `<td>`.
1+
The `sticky_left_column` prop expects an array of the column ids you want to be sticky. Make sure to add the corresponding id to the `<th>` and `<td>`.
22

3-
Please ensure that unique ids are used for all columns across multiple tables. Using the same columns ids on multiple tables can lead to issues when using the `stickyLeftColumn`.
3+
Please ensure that unique ids are used for all columns across multiple tables. Using the same columns ids on multiple tables can lead to issues when using the `sticky_left_column`.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<%= pb_rails("table", props: { size: "md", responsive: "scroll", sticky_right_column: ["13", "14", "15"] }) do %>
2+
<thead>
3+
<tr>
4+
<th>Column 1</th>
5+
<th>Column 2</th>
6+
<th>Column 3</th>
7+
<th>Column 4</th>
8+
<th>Column 5</th>
9+
<th>Column 6</th>
10+
<th>Column 7</th>
11+
<th>Column 8</th>
12+
<th>Column 9</th>
13+
<th>Column 10</th>
14+
<th>Column 11</th>
15+
<th>Column 12</th>
16+
<th id="13">Column 13</th>
17+
<th id="14">Column 14</th>
18+
<th id="15">Column 15</th>
19+
</tr>
20+
</thead>
21+
<tbody>
22+
<tr>
23+
<td>Value 1</td>
24+
<td>Value 2</td>
25+
<td>Value 3</td>
26+
<td>Value 4</td>
27+
<td>Value 5</td>
28+
<td>Value 6</td>
29+
<td>Value 7</td>
30+
<td>Value 8</td>
31+
<td>Value 9</td>
32+
<td>Value 10</td>
33+
<td>Value 11</td>
34+
<td>Value 12</td>
35+
<td id="13">Value 13</td>
36+
<td id="14">Value 14</td>
37+
<td id="15">Value 15</td>
38+
</tr>
39+
<tr>
40+
<td>Value 1</td>
41+
<td>Value 2</td>
42+
<td>Value 3</td>
43+
<td>Value 4</td>
44+
<td>Value 5</td>
45+
<td>Value 6</td>
46+
<td>Value 7</td>
47+
<td>Value 8</td>
48+
<td>Value 9</td>
49+
<td>Value 10</td>
50+
<td>Value 11</td>
51+
<td>Value 12</td>
52+
<td id="13">Value 13</td>
53+
<td id="14">Value 14</td>
54+
<td id="15">Value 15</td>
55+
</tr>
56+
<tr>
57+
<td>Value 1</td>
58+
<td>Value 2</td>
59+
<td>Value 3</td>
60+
<td>Value 4</td>
61+
<td>Value 5</td>
62+
<td>Value 6</td>
63+
<td>Value 7</td>
64+
<td>Value 8</td>
65+
<td>Value 9</td>
66+
<td>Value 10</td>
67+
<td>Value 11</td>
68+
<td>Value 12</td>
69+
<td id="13">Value 13</td>
70+
<td id="14">Value 14</td>
71+
<td id="15">Value 15</td>
72+
</tr>
73+
</tbody>
74+
<% end %>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The `sticky_right_column` prop works in the same way as the above `sticky_left_column` prop. It expects an array of the column ids you want to be sticky. Make sure to add the corresponding id to the `<th>` and `<td>`.
2+
3+
Please ensure that unique ids are used for all columns across multiple tables. Using the same columns ids on multiple tables can lead to issues when using the `sticky_right_column` prop.

playbook/app/pb_kits/playbook/pb_table/docs/example.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ examples:
55
- table_lg: Large
66
- table_sticky: Sticky Header
77
- table_sticky_left_columns: Sticky Left Column
8+
- table_sticky_right_columns: Sticky Right Column
9+
- table_sticky_columns: Sticky Left and Right Columns
810
- table_header: Table Header
911
- table_alignment_row_rails: Row Alignment
1012
- table_alignment_column_rails: Cell Alignment
Lines changed: 93 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,67 @@
11
import PbEnhancedElement from '../pb_enhanced_element'
22

3+
const TABLE_WRAPPER_SELECTOR = "[data-pb-table-wrapper]";
4+
35
export default class PbTable extends PbEnhancedElement {
4-
private stickyLeftColumns: string[] = [];
5-
private handleStickyLeftColumnsRef: () => void;
6+
stickyLeftColumns: string[] = [];
7+
stickyRightColumns: string[] = [];
8+
stickyRightColumnsReversed: string[] = [];
69

710
static get selector(): string {
8-
return '.table-responsive-collapse'
11+
return TABLE_WRAPPER_SELECTOR;
912
}
1013

11-
connect(): void {
12-
const tables = document.querySelectorAll('.table-responsive-collapse');
13-
// Each Table
14-
[].forEach.call(tables, (table: HTMLTableElement) => {
15-
// Header Titles
14+
connect() {
15+
if (this.element.classList.contains('table-responsive-collapse')) {
1616
const headers: string[] = [];
17-
[].forEach.call(table.querySelectorAll('th'), (header: HTMLTableCellElement) => {
17+
18+
[].forEach.call(this.element.querySelectorAll('th'), (header: HTMLTableCellElement) => {
1819
const colSpan = header.colSpan
1920
for (let i = 0; i < colSpan; i++) {
2021
headers.push(header.textContent.replace(/\r?\n|\r/, ''));
2122
}
2223
});
23-
// for each row in tbody
24-
[].forEach.call(table.querySelectorAll('tbody tr'), (row: HTMLTableRowElement) => {
25-
// for each cell
24+
25+
[].forEach.call(this.element.querySelectorAll('tbody tr'), (row: HTMLTableRowElement) => {
2626
[].forEach.call(row.cells, (cell: HTMLTableCellElement, headerIndex: number) => {
27-
// apply the attribute
2827
cell.setAttribute('data-title', headers[headerIndex])
2928
})
3029
})
31-
});
30+
}
3231

33-
// New sticky columns logic
3432
this.initStickyLeftColumns();
33+
this.initStickyRightColumns();
3534
}
3635

37-
private initStickyLeftColumns(): void {
38-
// Find tables with sticky-left-column class
39-
const tables = document.querySelectorAll('.sticky-left-column');
36+
initStickyLeftColumns() {
37+
const table = this.element.querySelector('.sticky-left-column');
4038

41-
tables.forEach((table) => {
42-
// Extract sticky left column IDs by looking at the component's class
39+
if (table) {
4340
const classList = Array.from(table.classList);
41+
const stickyColumnClass = classList.find(cls => cls.startsWith('sticky-left-columns-ids-'));
4442

45-
// Look for classes in the format sticky-left-column-{ids}
46-
const stickyColumnClass = classList.find(cls => cls.startsWith('sticky-columns-'));
4743
if (stickyColumnClass) {
48-
// Extract the IDs from the class name
4944
this.stickyLeftColumns = stickyColumnClass
50-
.replace('sticky-columns-', '')
51-
.split('-');
45+
.replace('sticky-left-columns-ids-', '')
46+
.split('-');
5247

5348
if (this.stickyLeftColumns.length > 0) {
5449
setTimeout(() => {
55-
this.handleStickyLeftColumnsRef = this.handleStickyLeftColumns.bind(this);
5650
this.handleStickyLeftColumns();
57-
window.addEventListener('resize', this.handleStickyLeftColumnsRef);
51+
window.addEventListener('resize', () => this.handleStickyLeftColumns());
5852
}, 10);
5953
}
6054
}
61-
});
55+
}
6256
}
6357

64-
private handleStickyLeftColumns(): void {
58+
handleStickyLeftColumns() {
6559
let accumulatedWidth = 0;
6660

6761
this.stickyLeftColumns.forEach((colId, index) => {
6862
const isLastColumn = index === this.stickyLeftColumns.length - 1;
69-
const header = document.querySelector(`th[id="${colId}"]`);
70-
const cells = document.querySelectorAll(`td[id="${colId}"]`);
63+
const header = this.element.querySelector(`th[id="${colId}"]`);
64+
const cells = this.element.querySelectorAll(`td[id="${colId}"]`);
7165

7266
if (header) {
7367
header.classList.add('sticky');
@@ -99,10 +93,75 @@ export default class PbTable extends PbEnhancedElement {
9993
});
10094
}
10195

96+
initStickyRightColumns() {
97+
const table = this.element.querySelector('.sticky-right-column');
98+
99+
if (table) {
100+
const classList = Array.from(table.classList);
101+
const stickyColumnClass = classList.find(cls => cls.startsWith('sticky-right-columns-ids-'));
102+
103+
if (stickyColumnClass) {
104+
this.stickyRightColumns = stickyColumnClass
105+
.replace('sticky-right-columns-ids-', '')
106+
.split('-');
107+
this.stickyRightColumnsReversed = this.stickyRightColumns.reverse();
108+
109+
if (this.stickyRightColumns.length > 0) {
110+
setTimeout(() => {
111+
this.handleStickyRightColumns();
112+
window.addEventListener('resize', () => this.handleStickyRightColumns());
113+
}, 10);
114+
}
115+
}
116+
}
117+
}
118+
119+
handleStickyRightColumns() {
120+
let accumulatedWidth = 0;
121+
122+
this.stickyRightColumnsReversed.forEach((colId, index) => {
123+
const isLastColumn = index === this.stickyRightColumns.length - 1;
124+
const header = this.element.querySelector(`th[id="${colId}"]`);
125+
const cells = this.element.querySelectorAll(`td[id="${colId}"]`);
126+
127+
if (header) {
128+
header.classList.add('sticky');
129+
(header as HTMLElement).style.right = `${accumulatedWidth}px`;
130+
131+
if (!isLastColumn) {
132+
header.classList.add('with-border-left');
133+
header.classList.remove('sticky-right-shadow');
134+
} else {
135+
header.classList.remove('with-border-right');
136+
header.classList.add('sticky-right-shadow');
137+
}
138+
139+
accumulatedWidth += (header as HTMLElement).offsetWidth;
140+
}
141+
142+
cells.forEach((cell) => {
143+
cell.classList.add('sticky');
144+
(cell as HTMLElement).style.right = `${accumulatedWidth - (header as HTMLElement).offsetWidth}px`;
145+
146+
if (!isLastColumn) {
147+
cell.classList.add('with-border-left');
148+
cell.classList.remove('sticky-right-shadow');
149+
} else {
150+
cell.classList.remove('with-border-left');
151+
cell.classList.add('sticky-right-shadow');
152+
}
153+
});
154+
});
155+
}
156+
102157
// Cleanup method to remove event listener
103-
disconnect(): void {
104-
if (this.handleStickyLeftColumnsRef) {
105-
window.removeEventListener('resize', this.handleStickyLeftColumnsRef);
158+
disconnect() {
159+
if (this.stickyLeftColumns.length > 0) {
160+
window.removeEventListener('resize', () => this.handleStickyLeftColumns());
161+
}
162+
163+
if (this.stickyRightColumns.length > 0) {
164+
window.removeEventListener('resize', () => this.handleStickyRightColumns());
106165
}
107166
}
108167
}

playbook/app/pb_kits/playbook/pb_table/table.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<% responsive_class = nil %>
55
<% end %>
66

7-
<%= content_tag(:div, class: responsive_class) do %>
7+
<%= content_tag(:div, class: responsive_class, 'data-pb-table-wrapper' => true) do %>
88
<% if object.tag == "table" %>
99
<%= content_tag(:table,
1010
aria: object.aria,

0 commit comments

Comments
 (0)