UNPKG

react-virtualized

Version:

React components for efficiently rendering large, scrollable lists and tabular data

280 lines (236 loc) 8.39 kB
import _extends from 'babel-runtime/helpers/extends'; import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties'; import _Object$getPrototypeOf from 'babel-runtime/core-js/object/get-prototype-of'; import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; import _createClass from 'babel-runtime/helpers/createClass'; import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn'; import _inherits from 'babel-runtime/helpers/inherits'; import React, { Component, PropTypes } from 'react'; import CollectionView from './CollectionView'; import _calculateSizeAndPositionData from './utils/calculateSizeAndPositionData'; import getUpdatedOffsetForIndex from '../utils/getUpdatedOffsetForIndex'; import shallowCompare from 'react-addons-shallow-compare'; /** * Renders scattered or non-linear data. * Unlike Grid, which renders checkerboard data, Collection can render arbitrarily positioned- even overlapping- data. */ var Collection = function (_Component) { _inherits(Collection, _Component); function Collection(props, context) { _classCallCheck(this, Collection); var _this = _possibleConstructorReturn(this, (Collection.__proto__ || _Object$getPrototypeOf(Collection)).call(this, props, context)); _this._cellMetadata = []; _this._lastRenderedCellIndices = []; // Cell cache during scroll (for perforamnce) _this._cellCache = []; _this._isScrollingChange = _this._isScrollingChange.bind(_this); return _this; } /** See Collection#recomputeCellSizesAndPositions */ _createClass(Collection, [{ key: 'recomputeCellSizesAndPositions', value: function recomputeCellSizesAndPositions() { this._cellCache = []; this._collectionView.recomputeCellSizesAndPositions(); } /** React lifecycle methods */ }, { key: 'render', value: function render() { var _this2 = this; var props = _objectWithoutProperties(this.props, []); return React.createElement(CollectionView, _extends({ cellLayoutManager: this, isScrollingChange: this._isScrollingChange, ref: function ref(_ref) { _this2._collectionView = _ref; } }, props)); } }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } /** CellLayoutManager interface */ }, { key: 'calculateSizeAndPositionData', value: function calculateSizeAndPositionData() { var _props = this.props; var cellCount = _props.cellCount; var cellSizeAndPositionGetter = _props.cellSizeAndPositionGetter; var sectionSize = _props.sectionSize; var data = _calculateSizeAndPositionData({ cellCount: cellCount, cellSizeAndPositionGetter: cellSizeAndPositionGetter, sectionSize: sectionSize }); this._cellMetadata = data.cellMetadata; this._sectionManager = data.sectionManager; this._height = data.height; this._width = data.width; } /** * Returns the most recently rendered set of cell indices. */ }, { key: 'getLastRenderedIndices', value: function getLastRenderedIndices() { return this._lastRenderedCellIndices; } /** * Calculates the minimum amount of change from the current scroll position to ensure the specified cell is (fully) visible. */ }, { key: 'getScrollPositionForCell', value: function getScrollPositionForCell(_ref2) { var align = _ref2.align; var cellIndex = _ref2.cellIndex; var height = _ref2.height; var scrollLeft = _ref2.scrollLeft; var scrollTop = _ref2.scrollTop; var width = _ref2.width; var cellCount = this.props.cellCount; if (cellIndex >= 0 && cellIndex < cellCount) { var cellMetadata = this._cellMetadata[cellIndex]; scrollLeft = getUpdatedOffsetForIndex({ align: align, cellOffset: cellMetadata.x, cellSize: cellMetadata.width, containerSize: width, currentOffset: scrollLeft, targetIndex: cellIndex }); scrollTop = getUpdatedOffsetForIndex({ align: align, cellOffset: cellMetadata.y, cellSize: cellMetadata.height, containerSize: height, currentOffset: scrollTop, targetIndex: cellIndex }); } return { scrollLeft: scrollLeft, scrollTop: scrollTop }; } }, { key: 'getTotalSize', value: function getTotalSize() { return { height: this._height, width: this._width }; } }, { key: 'cellRenderers', value: function cellRenderers(_ref3) { var _this3 = this; var height = _ref3.height; var isScrolling = _ref3.isScrolling; var width = _ref3.width; var x = _ref3.x; var y = _ref3.y; var _props2 = this.props; var cellGroupRenderer = _props2.cellGroupRenderer; var cellRenderer = _props2.cellRenderer; // Store for later calls to getLastRenderedIndices() this._lastRenderedCellIndices = this._sectionManager.getCellIndices({ height: height, width: width, x: x, y: y }); return cellGroupRenderer({ cellCache: this._cellCache, cellRenderer: cellRenderer, cellSizeAndPositionGetter: function cellSizeAndPositionGetter(_ref4) { var index = _ref4.index; return _this3._sectionManager.getCellMetadata({ index: index }); }, indices: this._lastRenderedCellIndices, isScrolling: isScrolling }); } }, { key: '_isScrollingChange', value: function _isScrollingChange(isScrolling) { if (!isScrolling) { this._cellCache = []; } } }]); return Collection; }(Component); Collection.propTypes = { 'aria-label': PropTypes.string, /** * Number of cells in Collection. */ cellCount: PropTypes.number.isRequired, /** * Responsible for rendering a group of cells given their indices. * Should implement the following interface: ({ * cellSizeAndPositionGetter:Function, * indices: Array<number>, * cellRenderer: Function * }): Array<PropTypes.node> */ cellGroupRenderer: PropTypes.func.isRequired, /** * Responsible for rendering a cell given an row and column index. * Should implement the following interface: ({ index: number, key: string, style: object }): PropTypes.element */ cellRenderer: PropTypes.func.isRequired, /** * Callback responsible for returning size and offset/position information for a given cell (index). * ({ index: number }): { height: number, width: number, x: number, y: number } */ cellSizeAndPositionGetter: PropTypes.func.isRequired, /** * Optionally override the size of the sections a Collection's cells are split into. */ sectionSize: PropTypes.number }; Collection.defaultProps = { 'aria-label': 'grid', cellGroupRenderer: defaultCellGroupRenderer }; export default Collection; function defaultCellGroupRenderer(_ref5) { var cellCache = _ref5.cellCache; var cellRenderer = _ref5.cellRenderer; var cellSizeAndPositionGetter = _ref5.cellSizeAndPositionGetter; var indices = _ref5.indices; var isScrolling = _ref5.isScrolling; return indices.map(function (index) { var cellMetadata = cellSizeAndPositionGetter({ index: index }); var cellRendererProps = { index: index, isScrolling: isScrolling, key: index, style: { height: cellMetadata.height, left: cellMetadata.x, position: 'absolute', top: cellMetadata.y, width: cellMetadata.width } }; // 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 (!(index in cellCache)) { cellCache[index] = cellRenderer(cellRendererProps); } return cellCache[index]; } else { return cellRenderer(cellRendererProps); } }).filter(function (renderedCell) { return !!renderedCell; }); }