Skip to content

Commit

Permalink
Merge pull request #144 from /issues/143
Browse files Browse the repository at this point in the history
Added additional properties to ScrollSync callback to enable scroll-driven UI changes
  • Loading branch information
bvaughn committed Mar 5, 2016
2 parents 0ac51b6 + c5238a5 commit dce9231
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 97 deletions.
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

0 comments on commit dce9231

Please sign in to comment.