Skip to content

feat: add support for dynamic row height #170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 60 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
22e8ce9
Initial add variableSizeGrid
jamesonhill Jun 5, 2020
203ae84
Fix dynamic row height prop calc
jamesonhill Jun 5, 2020
8e4c368
Randomize row heights
jamesonhill Jun 5, 2020
3266f22
Remove separate component for dynamicRows
jamesonhill Jun 5, 2020
21c5f39
Only wrap text for dynamicRows
jamesonhill Jun 5, 2020
1aa84d6
Cleanup TableRow
jamesonhill Jun 5, 2020
d61dd02
Cleanup GridTable
jamesonhill Jun 5, 2020
f6ca0fe
Cleanup BaseTable & example
jamesonhill Jun 5, 2020
65fdf74
Patch dynamic row heights on frozen columsn & rows
jamesonhill Jun 8, 2020
66484f0
Use pure component
jamesonhill Jun 8, 2020
6f0baeb
Cleanup logs & un-used props
jamesonhill Jun 8, 2020
f22b357
Avoid directly mutating rowSizeMap
jamesonhill Jun 8, 2020
f40639e
Recalculate rowHeight when rowData changes
jamesonhill Jun 8, 2020
5cc68e6
Use func expression instead of arrow func
jamesonhill Jun 9, 2020
bfc57af
Rename index to rowKey for heightMap
jamesonhill Jun 9, 2020
25dde0b
Remove un-used columnWidth prop
jamesonhill Jun 9, 2020
9b70865
Fix remaining arrow funcs
jamesonhill Jun 9, 2020
20d883b
Bump performance by resetting cache on rowKey
jamesonhill Jun 9, 2020
943eb6b
Make rowHeight optional func or number
jamesonhill Jun 9, 2020
d8a912d
Simplify props using estimatedRowHeight
jamesonhill Jun 9, 2020
afc1065
Only apply overflow class to non-dynamic table
jamesonhill Jun 9, 2020
6d28177
Remove context dependency
jamesonhill Jun 9, 2020
fc19a6f
Update examples
jamesonhill Jun 9, 2020
045dbf7
Remove extra dom node from TableRow
jamesonhill Jun 9, 2020
5811247
Fix rowKey to include depth
jamesonhill Jun 9, 2020
51ae9c2
Fix totalRowHeight
jamesonhill Jun 9, 2020
7771cee
Create func for measureRow
jamesonhill Jun 9, 2020
596162c
Remove un-used setHeight prop
jamesonhill Jun 9, 2020
e1f5ad6
Remove external rowHeight func prop
jamesonhill Jun 9, 2020
ef144db
Fix rowHeight interface
jamesonhill Jun 9, 2020
1391e06
Cleanup rowHeight prop
jamesonhill Jun 9, 2020
c0f1f83
Remove un-needed style
jamesonhill Jun 9, 2020
15d0733
Fix totalRowsHeight calculation for dynamic
jamesonhill Jun 10, 2020
a5b5254
Fix ref usage
jamesonhill Jun 10, 2020
53a36be
Revert changes to seed data
jamesonhill Jun 10, 2020
1f1b897
Spread row height props
jamesonhill Jun 10, 2020
2f2bb04
Only truncate cell text for non-dynamic table
jamesonhill Jun 11, 2020
887ce75
Remove unneeded style.height prop check
jamesonhill Jun 11, 2020
59c16fe
Remove innerRef prop
jamesonhill Jun 11, 2020
dd7742e
Move dynamic class to table container
jamesonhill Jun 11, 2020
91b73a8
Improve scroll performance
jamesonhill Jun 12, 2020
96b9710
Fix frozen rowHeight
jamesonhill Jun 12, 2020
045d511
Merge branch 'master' into 1.0.0-patch
nihgwu Jun 12, 2020
8a41fa8
minor changes
nihgwu Jun 12, 2020
d6b3b38
make it work with expansion
nihgwu Jun 12, 2020
511f322
debounce setState
nihgwu Jun 12, 2020
4609cc0
tweak
nihgwu Jun 12, 2020
e18dcc1
tweak
nihgwu Jun 12, 2020
0b722ea
work with frozen data
nihgwu Jun 12, 2020
0730ad8
handle size gets smaller
nihgwu Jun 12, 2020
f2e399b
fix loop measure
nihgwu Jun 16, 2020
4e9d0a0
use fixed rowHeight for frozen rows
nihgwu Jun 17, 2020
769d4b6
Fix column resizing
jamesonhill Jun 17, 2020
83bde83
Merge branch '1.0.0-patch' of github.com:AchieveIt/react-base-table i…
jamesonhill Jun 17, 2020
76c995e
tweak
nihgwu Jun 17, 2020
d579671
tweak
nihgwu Jun 20, 2020
c0dcf00
fix a weird issue on hover
nihgwu Jun 21, 2020
a4e4ae6
skip reset
nihgwu Jun 21, 2020
fec09b6
Merge branch 'master' into 1.0.0-patch
nihgwu Jun 21, 2020
eab8053
changelog
nihgwu Jun 21, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## NEXT VERSION

- feat: add `estimatedRowHeight` to support dynamic row height

## v1.9.4 (2020-06-22)

- chore: loosen prop type check for `data`
Expand Down
131 changes: 106 additions & 25 deletions src/BaseTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
cloneArray,
getValue,
throttle,
debounce,
noop,
} from './utils';

Expand Down Expand Up @@ -87,6 +88,7 @@ class BaseTable extends React.PureComponent {
this._handleColumnResizeStart = this._handleColumnResizeStart.bind(this);
this._handleColumnResizeStop = this._handleColumnResizeStop.bind(this);
this._handleColumnSort = this._handleColumnSort.bind(this);
this._handleRowHeightMeasured = this._handleRowHeightMeasured.bind(this);

this._getLeftTableContainerStyle = memoize(getContainerStyle);
this._getRightTableContainerStyle = memoize(getContainerStyle);
Expand All @@ -98,6 +100,25 @@ class BaseTable extends React.PureComponent {
this.columnManager.reset(columns, fixed);
}, isObjectEqual);

this._resetIndex = null;
this._rowHeightMap = {};
this._rowHeightMapBuffer = {};
this._getRowHeight = this._getRowHeight.bind(this);
this._updateRowHeights = debounce(() => {
if (
Object.keys(this._rowHeightMapBuffer).some(key => this._rowHeightMapBuffer[key] !== this._rowHeightMap[key])
) {
this._rowHeightMap = { ...this._rowHeightMap, ...this._rowHeightMapBuffer };
this.resetAfterRowIndex(this._resetIndex, false);
this._rowHeightMapBuffer = {};
this._resetIndex = null;
this.forceUpdateTable();
} else {
this._rowHeightMapBuffer = {};
this._resetIndex = null;
}
}, 0);

this._scroll = { scrollLeft: 0, scrollTop: 0 };
this._scrollHeight = 0;
this._lastScannedRowIndex = -1;
Expand Down Expand Up @@ -144,11 +165,31 @@ class BaseTable extends React.PureComponent {
};
}

/**
* Get the height of header
*/
getHeaderHeight() {
const { headerHeight } = this.props;
if (Array.isArray(headerHeight)) {
return headerHeight.reduce((sum, height) => sum + height, 0);
}
return headerHeight;
}

/**
* Get the total height of all frozen rows,
*/
getFrozenRowsHeight() {
const { frozenData, rowHeight } = this.props;
return frozenData.length * rowHeight;
}

/**
* Get the total height of all rows, including expanded rows.
*/
getTotalRowsHeight() {
return this._data.length * this.props.rowHeight;
const { rowHeight, estimatedRowHeight } = this.props;
return this.table ? this.table.getTotalRowsHeight() : this._data.length * (estimatedRowHeight || rowHeight);
}

/**
Expand All @@ -171,6 +212,24 @@ class BaseTable extends React.PureComponent {
this.rightTable && this.rightTable.forceUpdateTable();
}

/**
* Reset cached row heights after a specific rowIndex, should be used only in dynamic mode(estimatedRowHeight is provided)
*/
resetAfterRowIndex(rowIndex = 0, shouldForceUpdate = true) {
this.table && this.table.resetAfterRowIndex(rowIndex, shouldForceUpdate);
this.leftTable && this.leftTable.resetAfterRowIndex(rowIndex, shouldForceUpdate);
this.rightTable && this.rightTable.resetAfterRowIndex(rowIndex, shouldForceUpdate);
}

/**
* Reset cached column width, should be used only in dynamic mode(estimatedRowHeight is provided)
*/
resetColumnWidthCache(shouldForceUpdate = true) {
this.table && this.table.resetAfterColumnIndex(0, shouldForceUpdate);
this.leftTable && this.leftTable.resetAfterColumnIndex(0, shouldForceUpdate);
this.rightTable && this.rightTable.resetAfterColumnIndex(0, shouldForceUpdate);
}

/**
* Scroll to the specified offset.
* Useful for animating position changes.
Expand Down Expand Up @@ -257,7 +316,7 @@ class BaseTable extends React.PureComponent {
}

renderRow({ isScrolling, columns, rowData, rowIndex, style }) {
const { rowClassName, rowRenderer, rowEventHandlers, expandColumnKey } = this.props;
const { rowClassName, rowRenderer, rowEventHandlers, expandColumnKey, estimatedRowHeight } = this.props;

const rowClass = callOrReturn(rowClassName, { columns, rowData, rowIndex });
const extraProps = callOrReturn(this.props.rowProps, { columns, rowData, rowIndex });
Expand Down Expand Up @@ -287,11 +346,14 @@ class BaseTable extends React.PureComponent {
depth,
rowEventHandlers,
rowRenderer,
// for frozen rows we use fixed rowHeight
estimatedRowHeight: rowIndex >= 0 ? estimatedRowHeight : undefined,
cellRenderer: this.renderRowCell,
expandIconRenderer: this.renderExpandIcon,
onRowExpand: this._handleRowExpand,
// for fixed table, we need to sync the hover state across the inner tables
onRowHover: this.columnManager.hasFrozenColumns() ? this._handleRowHover : null,
onRowHeightMeasured: this._handleRowHeightMeasured,
};

return <TableRow {...rowProps} />;
Expand Down Expand Up @@ -446,7 +508,7 @@ class BaseTable extends React.PureComponent {
}

renderMainTable() {
const { width, headerHeight, rowHeight, fixed, ...rest } = this.props;
const { width, headerHeight, rowHeight, fixed, estimatedRowHeight, ...rest } = this.props;
const height = this._getTableHeight();

let tableWidth = width - this._verticalScrollbarSize;
Expand All @@ -467,6 +529,8 @@ class BaseTable extends React.PureComponent {
height={height}
headerHeight={headerHeight}
rowHeight={rowHeight}
estimatedRowHeight={estimatedRowHeight}
getRowHeight={estimatedRowHeight ? this._getRowHeight : undefined}
headerWidth={tableWidth + (fixed ? this._verticalScrollbarSize : 0)}
bodyWidth={tableWidth}
headerRenderer={this.renderHeader}
Expand All @@ -480,7 +544,7 @@ class BaseTable extends React.PureComponent {
renderLeftTable() {
if (!this.columnManager.hasLeftFrozenColumns()) return null;

const { width, headerHeight, rowHeight, ...rest } = this.props;
const { width, headerHeight, rowHeight, estimatedRowHeight, ...rest } = this.props;

const containerHeight = this._getFrozenContainerHeight();
const offset = this._verticalScrollbarSize || 20;
Expand All @@ -498,6 +562,8 @@ class BaseTable extends React.PureComponent {
height={containerHeight}
headerHeight={headerHeight}
rowHeight={rowHeight}
estimatedRowHeight={estimatedRowHeight}
getRowHeight={estimatedRowHeight ? this._getRowHeight : undefined}
headerWidth={columnsWidth + offset}
bodyWidth={columnsWidth + offset}
headerRenderer={this.renderHeader}
Expand All @@ -511,7 +577,7 @@ class BaseTable extends React.PureComponent {
renderRightTable() {
if (!this.columnManager.hasRightFrozenColumns()) return null;

const { width, headerHeight, rowHeight, ...rest } = this.props;
const { width, headerHeight, rowHeight, estimatedRowHeight, ...rest } = this.props;

const containerHeight = this._getFrozenContainerHeight();
const columnsWidth = this.columnManager.getRightFrozenColumnsWidth();
Expand All @@ -529,6 +595,8 @@ class BaseTable extends React.PureComponent {
height={containerHeight}
headerHeight={headerHeight}
rowHeight={rowHeight}
estimatedRowHeight={estimatedRowHeight}
getRowHeight={estimatedRowHeight ? this._getRowHeight : undefined}
headerWidth={columnsWidth + scrollbarWidth}
bodyWidth={columnsWidth}
headerRenderer={this.renderHeader}
Expand Down Expand Up @@ -582,7 +650,7 @@ class BaseTable extends React.PureComponent {
const { data, frozenData, footerHeight, emptyRenderer } = this.props;

if ((data && data.length) || (frozenData && frozenData.length)) return null;
const headerHeight = this._getHeaderHeight();
const headerHeight = this.getHeaderHeight();
return (
<div className={this._prefixClass('empty-layer')} style={{ top: headerHeight, bottom: footerHeight }}>
{renderElement(emptyRenderer)}
Expand Down Expand Up @@ -610,8 +678,8 @@ class BaseTable extends React.PureComponent {
style,
footerHeight,
classPrefix,
estimatedRowHeight,
} = this.props;

this._resetColumnManager(getColumns(columns, children), fixed);

if (expandColumnKey) {
Expand All @@ -635,6 +703,7 @@ class BaseTable extends React.PureComponent {
[`${classPrefix}--has-frozen-rows`]: frozenData.length > 0,
[`${classPrefix}--has-frozen-columns`]: this.columnManager.hasFrozenColumns(),
[`${classPrefix}--disabled`]: disabled,
[`${classPrefix}--dynamic`]: estimatedRowHeight > 0,
});
return (
<div ref={this._setContainerRef} className={cls} style={containerStyle}>
Expand Down Expand Up @@ -694,27 +763,20 @@ class BaseTable extends React.PureComponent {
return DEFAULT_COMPONENTS[name];
}

_getHeaderHeight() {
const { headerHeight } = this.props;
if (Array.isArray(headerHeight)) {
return headerHeight.reduce((sum, height) => sum + height, 0);
}
return headerHeight;
}

_getFrozenRowsHeight() {
const { frozenData, rowHeight } = this.props;
return frozenData.length * rowHeight;
// for dynamic row height
_getRowHeight(rowIndex) {
const { estimatedRowHeight, rowKey } = this.props;
return this._rowHeightMap[this._data[rowIndex][rowKey]] || estimatedRowHeight;
}

_getTableHeight() {
const { height, maxHeight, footerHeight } = this.props;
let tableHeight = height - footerHeight;

if (maxHeight > 0) {
const frozenRowsHeight = this._getFrozenRowsHeight();
const frozenRowsHeight = this.getFrozenRowsHeight();
const totalRowsHeight = this.getTotalRowsHeight();
const headerHeight = this._getHeaderHeight();
const headerHeight = this.getHeaderHeight();
const totalHeight = headerHeight + frozenRowsHeight + totalRowsHeight + this._horizontalScrollbarSize;
tableHeight = Math.min(totalHeight, maxHeight - footerHeight);
}
Expand All @@ -723,7 +785,7 @@ class BaseTable extends React.PureComponent {
}

_getBodyHeight() {
return this._getTableHeight() - this._getHeaderHeight() - this._getFrozenRowsHeight();
return this._getTableHeight() - this.getHeaderHeight() - this.getFrozenRowsHeight();
}

_getFrozenContainerHeight() {
Expand All @@ -733,7 +795,7 @@ class BaseTable extends React.PureComponent {
// in auto height mode tableHeight = totalHeight
if (maxHeight > 0) return tableHeight;

const totalHeight = this.getTotalRowsHeight() + this._getHeaderHeight() + this._getFrozenRowsHeight();
const totalHeight = this.getTotalRowsHeight() + this.getHeaderHeight() + this.getFrozenRowsHeight();
return Math.min(tableHeight, totalHeight);
}

Expand Down Expand Up @@ -821,8 +883,8 @@ class BaseTable extends React.PureComponent {

_handleVerticalScroll({ scrollTop }) {
const lastScrollTop = this._scroll.scrollTop;
this.scrollToTop(scrollTop);

if (scrollTop !== lastScrollTop) this.scrollToTop(scrollTop);
if (scrollTop > lastScrollTop) this._maybeCallOnEndReached();
}

Expand Down Expand Up @@ -861,6 +923,10 @@ class BaseTable extends React.PureComponent {
this.columnManager.setColumnWidth(key, width);
this.setState({ resizingWidth: width });

if (this.props.estimatedRowHeight && this.props.fixed) {
this.resetColumnWidthCache(false);
}

const column = this.columnManager.getColumn(key);
this.props.onColumnResize({ column, width });
}
Expand Down Expand Up @@ -893,6 +959,17 @@ class BaseTable extends React.PureComponent {
const column = this.columnManager.getColumn(key);
onColumnSort({ column, key, order });
}

_handleRowHeightMeasured(rowKey, size, rowIndex) {
if (this._resetIndex === null) this._resetIndex = rowIndex;
else if (this._resetIndex > rowIndex) this._resetIndex = rowIndex;

if (!this._rowHeightMapBuffer[rowKey] || this._rowHeightMapBuffer[rowKey] < size) {
this._rowHeightMapBuffer[rowKey] = size;
}

this._updateRowHeights();
}
}

BaseTable.Column = Column;
Expand Down Expand Up @@ -971,9 +1048,13 @@ BaseTable.propTypes = {
*/
maxHeight: PropTypes.number,
/**
* The height of each table row
* The height of each table row, will be only used by frozen rows if `estimatedRowHeight` is set
*/
rowHeight: PropTypes.number,
/**
* Estimated row height, the real height will be measure dynamically according to the content
*/
rowHeight: PropTypes.number.isRequired,
estimatedRowHeight: PropTypes.number,
/**
* The height of the table header, set to 0 to hide the header, could be an array to render multi headers.
*/
Expand Down
Loading