Skip to content
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

Added additional properties to ScrollSync callback to enable scroll-driven UI changes #144

Merged
merged 3 commits into from
Mar 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 7 additions & 3 deletions docs/ScrollSync.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ The child function is passed the following named parameters:

| Parameter | Type | Description |
|:---|:---|:---:|
| clientHeight | Number | Height of the visible portion of the `Grid` (or other scroll-synced component) |
| clientWidth | Number | Width of the visible portion of the `Grid` (or other scroll-synced component) |
| onScroll | Function | This function should be passed through to at least one of the virtualized child components. Updates to it will trigger updates to the scroll ofset parameters which will in turn update the other virtualized children. |
| scrollLeft | Number | The current left scroll offset. |
| scrollTop | Number | The current top scroll offset. |
| scrollHeight | Number | Total height of all rows in the `Grid` (or other scroll-synced component) |
| scrollLeft | Number | The current scroll-left offset. |
| scrollTop | Number | The current scroll-top offset. |
| scrollWidth | Number | Total width of all rows in the `Grid` (or other scroll-synced component) |

### Examples

Expand All @@ -28,7 +32,7 @@ import { Grid, ScrollSync, VirtualScroll } from 'react-virtualized'
function render (props) {
return (
<ScrollSync>
{({ onScroll, scrollLeft, scrollTop }) => (
{({ clientHeight, clientWidth, scrollHeight, scrollLeft, scrollTop, scrollWidth }) => (
<div className='Table'>
<div className='LeftColumn'>
<VirtualScroll
Expand Down
17 changes: 15 additions & 2 deletions source/FlexTable/FlexTable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,18 @@ describe('FlexTable', () => {
})

describe('onScroll', () => {
it('should trigger callback when component initially mounts', () => {
const onScrollCalls = []
renderTable({
onScroll: params => onScrollCalls.push(params)
})
expect(onScrollCalls).toEqual([{
clientHeight: 80,
scrollHeight: 1000,
scrollTop: 0
}])
})

it('should trigger callback when component scrolls', () => {
const onScrollCalls = []
const table = renderTable({
Expand All @@ -662,11 +674,12 @@ describe('FlexTable', () => {
}
table.refs.Grid.refs.scrollingContainer = target // HACK to work around _onScroll target check
Simulate.scroll(findDOMNode(table.refs.Grid), { target })
expect(onScrollCalls).toEqual([{
expect(onScrollCalls.length).toEqual(2)
expect(onScrollCalls[1]).toEqual({
clientHeight: 80,
scrollHeight: 1000,
scrollTop: 100
}])
})
})
})

Expand Down
48 changes: 31 additions & 17 deletions source/Grid/Grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ export default class Grid extends Component {

// Update onRowsRendered callback
this._invokeOnGridRenderedHelper()

// Initialize onScroll callback
this._invokeOnScrollMemoizer({
scrollLeft: scrollLeft || 0,
scrollTop: scrollTop || 0,
totalColumnsWidth: this._getTotalColumnsWidth(),
totalRowsHeight: this._getTotalRowsHeight()
})
}

componentDidUpdate (prevProps, prevState) {
Expand Down Expand Up @@ -606,6 +614,27 @@ export default class Grid extends Component {
})
}

_invokeOnScrollMemoizer ({ scrollLeft, scrollTop, totalColumnsWidth, totalRowsHeight }) {
const { height, onScroll, width } = this.props

this._onScrollMemoizer({
callback: ({ scrollLeft, scrollTop }) => {
onScroll({
clientHeight: height,
clientWidth: width,
scrollHeight: totalRowsHeight,
scrollLeft,
scrollTop,
scrollWidth: totalColumnsWidth
})
},
indices: {
scrollLeft,
scrollTop
}
})
}

/**
* Updates the state during the next animation frame.
* Use this method to avoid multiple renders in a small span of time.
Expand Down Expand Up @@ -773,7 +802,7 @@ export default class Grid extends Component {
// Gradually converging on a scrollTop that is within the bounds of the new, smaller height.
// This causes a series of rapid renders that is slow for long lists.
// We can avoid that by doing some simple bounds checking to ensure that scrollTop never exceeds the total height.
const { height, onScroll, width } = this.props
const { height, width } = this.props
const totalRowsHeight = this._getTotalRowsHeight()
const totalColumnsWidth = this._getTotalColumnsWidth()
const scrollLeft = Math.min(totalColumnsWidth - width, event.target.scrollLeft)
Expand Down Expand Up @@ -809,21 +838,6 @@ export default class Grid extends Component {
})
}

this._onScrollMemoizer({
callback: ({ scrollLeft, scrollTop }) => {
onScroll({
clientHeight: height,
clientWidth: width,
scrollHeight: totalRowsHeight,
scrollLeft,
scrollTop,
scrollWidth: totalColumnsWidth
})
},
indices: {
scrollLeft,
scrollTop
}
})
this._invokeOnScrollMemoizer({ scrollLeft, scrollTop, totalColumnsWidth, totalRowsHeight })
}
}
29 changes: 24 additions & 5 deletions source/Grid/Grid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,23 @@ describe('Grid', () => {
Simulate.scroll(findDOMNode(grid), { target })
}

it('should trigger callback when component is mounted', () => {
const onScrollCalls = []
renderGrid({
onScroll: params => onScrollCalls.push(params),
scrollLeft: 50,
scrollTop: 100
})
expect(onScrollCalls).toEqual([{
clientHeight: 100,
clientWidth: 200,
scrollHeight: 2000,
scrollLeft: 50,
scrollTop: 100,
scrollWidth: 2500
}])
})

it('should trigger callback when component scrolls horizontally', () => {
const onScrollCalls = []
const grid = renderGrid({
Expand All @@ -407,17 +424,18 @@ describe('Grid', () => {
scrollLeft: 100,
scrollTop: 0
})
expect(onScrollCalls).toEqual([{
expect(onScrollCalls.length).toEqual(2)
expect(onScrollCalls[1]).toEqual({
clientHeight: 100,
clientWidth: 200,
scrollHeight: 2000,
scrollLeft: 100,
scrollTop: 0,
scrollWidth: 2500
}])
})
})

it('should trigger callback when component scrolls horizontally', () => {
it('should trigger callback when component scrolls vertically', () => {
const onScrollCalls = []
const grid = renderGrid({
onScroll: params => onScrollCalls.push(params)
Expand All @@ -427,14 +445,15 @@ describe('Grid', () => {
scrollLeft: 0,
scrollTop: 100
})
expect(onScrollCalls).toEqual([{
expect(onScrollCalls.length).toEqual(2)
expect(onScrollCalls[1]).toEqual({
clientHeight: 100,
clientWidth: 200,
scrollHeight: 2000,
scrollLeft: 0,
scrollTop: 100,
scrollWidth: 2500
}])
})
})
})

Expand Down
2 changes: 0 additions & 2 deletions source/ScrollSync/ScrollSync.example.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
.HeaderGrid {
width: 100%;
overflow: hidden !important;
background-color: #fafafa;
}
.BodyGrid {
width: 100%;
Expand All @@ -43,6 +42,5 @@
}
.headerCell,
.leftCell {
background-color: #fafafa;
font-weight: bold;
}
139 changes: 84 additions & 55 deletions source/ScrollSync/ScrollSync.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ export default class GridExample extends Component {

this.state = {
columnWidth: 75,
columnsCount: 1000,
columnsCount: 50,
height: 300,
overscanColumnsCount: 0,
overscanRowsCount: 5,
rowHeight: 40,
rowsCount: 1000
rowsCount: 100
}

this._renderBodyCell = this._renderBodyCell.bind(this)
Expand Down Expand Up @@ -62,65 +62,86 @@ export default class GridExample extends Component {

<ContentBoxParagraph>
This example shows two <code>Grid</code>s and one <code>VirtualScroll</code> configured to mimic a spreadsheet with a fixed header and first column.
It also shows how a scroll callback can be used to control UI properties such as background color.
</ContentBoxParagraph>

<ScrollSync>
{({ onScroll, scrollLeft, scrollTop }) => (
<div className={styles.GridRow}>
<div
className={styles.LeftSideGridContainer}
style={{ marginTop: rowHeight }}
>
<VirtualScroll
className={styles.LeftSideGrid}
height={height}
overscanRowsCount={overscanRowsCount}
rowHeight={rowHeight}
rowRenderer={this._renderLeftSideCell}
rowsCount={rowsCount}
scrollTop={scrollTop}
width={columnWidth}
/>
</div>
<div className={styles.GridColumn}>
<AutoSizer disableHeight>
{({ width }) => (
<div>
<div>
<Grid
className={styles.HeaderGrid}
columnWidth={columnWidth}
columnsCount={columnsCount}
height={rowHeight}
overscanColumnsCount={overscanColumnsCount}
renderCell={this._renderHeaderCell}
rowHeight={rowHeight}
rowsCount={1}
scrollLeft={scrollLeft}
width={width}
/>
</div>
{({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth }) => {
const x = scrollLeft / (scrollWidth - clientWidth)
const y = scrollTop / (scrollHeight - clientHeight)

const leftBackgroundColor = fadeBetweenRGB([65, 65, 65], [0, 0, 0], y)
const leftColor = `#ffffff`

const topBackgroundColor = fadeBetweenRGB([77, 182, 172], [23, 146, 135], x)
const topColor = `#ffffff`

return (
<div className={styles.GridRow}>
<div
className={styles.LeftSideGridContainer}
style={{
backgroundColor: leftBackgroundColor,
color: leftColor,
marginTop: rowHeight
}}
>
<VirtualScroll
className={styles.LeftSideGrid}
height={height}
overscanRowsCount={overscanRowsCount}
rowHeight={rowHeight}
rowRenderer={this._renderLeftSideCell}
rowsCount={rowsCount}
scrollTop={scrollTop}
width={columnWidth}
/>
</div>
<div className={styles.GridColumn}>
<AutoSizer disableHeight>
{({ width }) => (
<div>
<Grid
className={styles.BodyGrid}
columnWidth={columnWidth}
columnsCount={columnsCount}
height={height}
onScroll={onScroll}
overscanColumnsCount={overscanColumnsCount}
overscanRowsCount={overscanRowsCount}
renderCell={this._renderBodyCell}
rowHeight={rowHeight}
rowsCount={rowsCount}
width={width}
/>
<div style={{
backgroundColor: topBackgroundColor,
color: topColor,
height: rowHeight,
width
}}>
<Grid
className={styles.HeaderGrid}
columnWidth={columnWidth}
columnsCount={columnsCount}
height={rowHeight}
overscanColumnsCount={overscanColumnsCount}
renderCell={this._renderHeaderCell}
rowHeight={rowHeight}
rowsCount={1}
scrollLeft={scrollLeft}
width={width}
/>
</div>
<div>
<Grid
className={styles.BodyGrid}
columnWidth={columnWidth}
columnsCount={columnsCount}
height={height}
onScroll={onScroll}
overscanColumnsCount={overscanColumnsCount}
overscanRowsCount={overscanRowsCount}
renderCell={this._renderBodyCell}
rowHeight={rowHeight}
rowsCount={rowsCount}
width={width}
/>
</div>
</div>
</div>
)}
</AutoSizer>
)}
</AutoSizer>
</div>
</div>
</div>
)}
)
}}
</ScrollSync>
</ContentBox>
)
Expand Down Expand Up @@ -157,3 +178,11 @@ export default class GridExample extends Component {
)
}
}

function fadeBetweenRGB (rgbFrom, rgbTo, amount) {
const r = Math.round(rgbFrom[0] + (rgbTo[0] - rgbFrom[0]) * amount)
const g = Math.round(rgbFrom[1] + (rgbTo[1] - rgbFrom[1]) * amount)
const b = Math.round(rgbFrom[2] + (rgbTo[2] - rgbFrom[2]) * amount)

return `rgb(${r}, ${g}, ${b})`
}
Loading