Skip to content

Instantly share code, notes, and snippets.

@bvaughn
Created May 14, 2016 20:20
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bvaughn/3a358dda3654e1e93fba35890a093c19 to your computer and use it in GitHub Desktop.
Save bvaughn/3a358dda3654e1e93fba35890a093c19 to your computer and use it in GitHub Desktop.
Example Grid with columns and rows, built using :cellRangeRenderer (with react-virtualized 7.x)
/** @flow */
import Immutable from 'immutable'
import React, { Component, PropTypes } from 'react'
import { ContentBox, ContentBoxHeader, ContentBoxParagraph } from '../demo/ContentBox'
import { LabeledInput, InputRow } from '../demo/LabeledInput'
import AutoSizer from '../AutoSizer'
import Grid from './Grid'
import shallowCompare from 'react-addons-shallow-compare'
import cn from 'classnames'
import styles from './Grid.example.css'
const FIXED_CELL_ZINDEX = 2
const FIXED_LEFT_COLUMN_WIDTH = 50
const FIXED_TOP_ROW_HEIGHT = 40
export default class GridExample extends Component {
static propTypes = {
list: PropTypes.instanceOf(Immutable.List).isRequired
}
constructor (props, context) {
super(props, context)
this.state = {
columnWidth: 100,
columnCount: 1000,
height: 300,
rowHeight: 40,
rowCount: 1000,
scrollToColumn: undefined,
scrollToRow: undefined,
useDynamicRowHeight: false
}
this._cellRangeRenderer = this._cellRangeRenderer.bind(this)
this._cellRenderer = this._cellRenderer.bind(this)
this._getColumnWidth = this._getColumnWidth.bind(this)
this._getRowClassName = this._getRowClassName.bind(this)
this._getRowHeight = this._getRowHeight.bind(this)
}
render () {
const {
columnCount,
height,
rowHeight,
rowCount,
scrollToColumn,
scrollToRow,
useDynamicRowHeight
} = this.state
return (
<ContentBox {...this.props}>
<ContentBoxHeader
text='Grid'
sourceLink='https://github.com/bvaughn/react-virtualized/blob/master/source/Grid/Grid.example.js'
docsLink='https://github.com/bvaughn/react-virtualized/blob/master/docs/Grid.md'
/>
<AutoSizer disableHeight>
{({ width }) => (
<Grid
cellRangeRenderer={this._cellRangeRenderer}
cellRenderer={this._cellRenderer}
className={styles.BodyGrid}
columnWidth={this._getColumnWidth}
columnCount={columnCount}
height={height}
overscanColumnCount={0}
overscanRowCount={0}
rowHeight={useDynamicRowHeight ? this._getRowHeight : rowHeight}
rowCount={rowCount}
scrollToColumn={scrollToColumn}
scrollToRow={scrollToRow}
width={width}
/>
)}
</AutoSizer>
</ContentBox>
)
}
shouldComponentUpdate (nextProps, nextState) {
return shallowCompare(this, nextProps, nextState)
}
// Forked from defaultCellRangeRenderer.js
_cellRangeRenderer ({
cellCache,
cellRenderer,
columnSizeAndPositionManager,
columnStartIndex,
columnStopIndex,
isScrolling,
rowSizeAndPositionManager,
rowStartIndex,
rowStopIndex,
scrollLeft,
scrollTop
}) {
const renderedCells = []
// Top-left corner piece
renderedCells.push(
<div
key='fixed-fixed'
className={cn('Grid__cell', styles.cell, styles.topLeftCell)}
style={{
height: FIXED_TOP_ROW_HEIGHT,
left: scrollLeft,
position: 'fixed',
top: scrollTop,
width: FIXED_LEFT_COLUMN_WIDTH,
zIndex: FIXED_CELL_ZINDEX + 1
}}
>
&nbsp;
</div>
)
// Render fixed header row
for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
let columnDatum = columnSizeAndPositionManager.getSizeAndPositionOfCell(columnIndex)
renderedCells.push(
<div
key={`fixed-${columnIndex}`}
className={cn('Grid__cell', styles.cell, styles.headerCell)}
style={{
height: FIXED_TOP_ROW_HEIGHT,
left: columnDatum.offset + FIXED_LEFT_COLUMN_WIDTH,
position: 'fixed',
top: scrollTop,
width: columnDatum.size,
zIndex: FIXED_CELL_ZINDEX
}}
>
H{columnIndex}
</div>
)
}
// Render fixed left column
for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
let rowDatum = rowSizeAndPositionManager.getSizeAndPositionOfCell(rowIndex)
let datum = this._getDatum(rowIndex)
renderedCells.push(
<div
key={`${rowIndex}-fixed`}
className={cn('Grid__cell', styles.cell, styles.letterCell)}
style={{
backgroundColor: datum.color,
height: rowDatum.size,
left: scrollLeft,
position: 'fixed',
top: rowDatum.offset + FIXED_TOP_ROW_HEIGHT,
width: FIXED_LEFT_COLUMN_WIDTH,
zIndex: FIXED_CELL_ZINDEX
}}
>
{datum.name.charAt(0)}
</div>
)
}
// Forked from defaultCellRangeRenderer.js
for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) {
let rowDatum = rowSizeAndPositionManager.getSizeAndPositionOfCell(rowIndex)
for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) {
let columnDatum = columnSizeAndPositionManager.getSizeAndPositionOfCell(columnIndex)
let key = `${rowIndex}-${columnIndex}`
let renderedCell
// Avoid re-creating cells while scrolling.
// This can lead to the same cell being created many times and can cause performance issues for "heavy" cells.
// If a scroll is in progress- cache and reuse cells.
// This cache will be thrown away once scrolling complets.
if (isScrolling) {
if (!cellCache[key]) {
cellCache[key] = cellRenderer({
columnIndex,
isScrolling,
rowIndex
})
}
renderedCell = cellCache[key]
// If the user is no longer scrolling, don't cache cells.
// This makes dynamic cell content difficult for users and would also lead to a heavier memory footprint.
} else {
renderedCell = cellRenderer({
columnIndex,
isScrolling,
rowIndex
})
}
if (renderedCell == null || renderedCell === false) {
continue
}
let child = (
<div
key={key}
className='Grid__cell'
style={{
height: rowDatum.size,
left: columnDatum.offset + FIXED_LEFT_COLUMN_WIDTH,
position: 'fixed',
top: rowDatum.offset + FIXED_TOP_ROW_HEIGHT,
width: columnDatum.size
}}
>
{renderedCell}
</div>
)
renderedCells.push(child)
}
}
return renderedCells
}
_cellRenderer ({ columnIndex, rowIndex }) {
const rowClass = this._getRowClassName(rowIndex)
const datum = this._getDatum(rowIndex)
let content
switch (columnIndex) {
case 0:
content = datum.name
break
case 1:
content = datum.random
break
default:
content = (
<div>
c:{columnIndex}
<br />
r:{rowIndex}
</div>
)
break
}
const classNames = cn(rowClass, styles.cell, {
[styles.centeredCell]: columnIndex > 2
})
return (
<div className={classNames}>
{content}
</div>
)
}
_getColumnWidth ({ index }) {
switch (index) {
case 0:
return 100
case 1:
return 300
default:
return 50
}
}
_getDatum (index) {
const { list } = this.props
return list.get(index % list.size)
}
_getRowClassName (row) {
return row % 2 === 0 ? styles.evenRow : styles.oddRow
}
_getRowHeight ({ index }) {
return this._getDatum(index).size
}
}
@bvaughn
Copy link
Author

bvaughn commented May 14, 2016

CSS here:

.GridRow {
  margin-top: 15px;
  display: flex;
  flex-direction: row;
}
.GridColumn {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}
.LeftSideGridContainer {
  flex: 0 0 50px;
}

.BodyGrid {
  width: 100%;
  border: 1px solid #e0e0e0;
}

.evenRow,
.oddRow {
  border-bottom: 1px solid #e0e0e0;
}
.oddRow {
  background-color: #fafafa;
}

.cell,
.headerCell {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 .5em;
}
.cell {
  border-right: 1px solid #e0e0e0;
  border-bottom: 1px solid #e0e0e0;
}
.headerCell {
  font-weight: bold;
  border-right: 1px solid #ccc;
  background-color: #e0e0e0;
}
.centeredCell {
  align-items: center;
  text-align: center;
}
.topLeftCell {
  border-right: 1px solid #e0e0e0;
  border-bottom: 1px solid #e0e0e0;
  background-color: #fff;
}

.letterCell {
  font-size: 1.5em;
  color: #fff;
  text-align: center;
}

.noCells {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1em;
  color: #bdbdbd;
}

@saivikas7875
Copy link

If anyone gets to this example and they are facing scrolling issues, its probably because of the position: fixed property in the _cellRangeRenderer cells. Update those to postion: absolute and scrolling should be good

Places where it needs updating -

@vinayaknagpal
Copy link

vinayaknagpal commented Sep 19, 2016

The fixed cells flicker because scrollLeft, scrollTop are not updated smoothly by the browser.
Using pure logic scrolling seems to be the only way to resolve the flickering and achieve a smooth scrolling experience.

I ended up using
https://github.com/gooddata/zynga-scroller-es6 and wrapped the grid in a component similar to https://github.com/facebook/fixed-data-table/blob/master/site/examples/TouchableArea.js

@bvaughn
Copy link
Author

bvaughn commented Sep 19, 2016

Hey @vinayaknagpal. Any chance you'd be willing to share a Gist or Plunker of your react-virtualized + zynga-scroller-es6 combination? 😄

@vinayaknagpal
Copy link

@bvaughn
Sure. Will put together an example and share in a few days. Racing a release deadline at the moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment