UNPKG

react-virtualized

Version:

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

292 lines (235 loc) 8.59 kB
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 shallowCompare from 'react-addons-shallow-compare'; import ReactDOM from 'react-dom'; import CellSizeCache from './defaultCellSizeCache'; /** * Measures a Grid cell's contents by rendering them in a way that is not visible to the user. * Either a fixed width or height may be provided if it is desirable to measure only in one direction. */ var CellMeasurer = function (_Component) { _inherits(CellMeasurer, _Component); function CellMeasurer(props, state) { _classCallCheck(this, CellMeasurer); var _this = _possibleConstructorReturn(this, (CellMeasurer.__proto__ || _Object$getPrototypeOf(CellMeasurer)).call(this, props, state)); _this._cellSizeCache = props.cellSizeCache || new CellSizeCache(); _this.getColumnWidth = _this.getColumnWidth.bind(_this); _this.getRowHeight = _this.getRowHeight.bind(_this); _this.resetMeasurements = _this.resetMeasurements.bind(_this); _this.resetMeasurementForColumn = _this.resetMeasurementForColumn.bind(_this); _this.resetMeasurementForRow = _this.resetMeasurementForRow.bind(_this); return _this; } _createClass(CellMeasurer, [{ key: 'getColumnWidth', value: function getColumnWidth(_ref) { var index = _ref.index; if (this._cellSizeCache.hasColumnWidth(index)) { return this._cellSizeCache.getColumnWidth(index); } var rowCount = this.props.rowCount; var maxWidth = 0; for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) { var _measureCell2 = this._measureCell({ clientWidth: true, columnIndex: index, rowIndex: rowIndex }); var width = _measureCell2.width; maxWidth = Math.max(maxWidth, width); } this._cellSizeCache.setColumnWidth(index, maxWidth); return maxWidth; } }, { key: 'getRowHeight', value: function getRowHeight(_ref2) { var index = _ref2.index; if (this._cellSizeCache.hasRowHeight(index)) { return this._cellSizeCache.getRowHeight(index); } var columnCount = this.props.columnCount; var maxHeight = 0; for (var columnIndex = 0; columnIndex < columnCount; columnIndex++) { var _measureCell3 = this._measureCell({ clientHeight: true, columnIndex: columnIndex, rowIndex: index }); var height = _measureCell3.height; maxHeight = Math.max(maxHeight, height); } this._cellSizeCache.setRowHeight(index, maxHeight); return maxHeight; } }, { key: 'resetMeasurementForColumn', value: function resetMeasurementForColumn(columnIndex) { this._cellSizeCache.clearColumnWidth(columnIndex); } }, { key: 'resetMeasurementForRow', value: function resetMeasurementForRow(rowIndex) { this._cellSizeCache.clearRowHeight(rowIndex); } }, { key: 'resetMeasurements', value: function resetMeasurements() { this._cellSizeCache.clearAllColumnWidths(); this._cellSizeCache.clearAllRowHeights(); } }, { key: 'componentDidMount', value: function componentDidMount() { this._renderAndMount(); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { var cellSizeCache = this.props.cellSizeCache; if (cellSizeCache !== nextProps.cellSizeCache) { this._cellSizeCache = nextProps.cellSizeCache; } this._updateDivDimensions(nextProps); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this._unmountContainer(); } }, { key: 'render', value: function render() { var children = this.props.children; return children({ getColumnWidth: this.getColumnWidth, getRowHeight: this.getRowHeight, resetMeasurements: this.resetMeasurements, resetMeasurementForColumn: this.resetMeasurementForColumn, resetMeasurementForRow: this.resetMeasurementForRow }); } }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); } }, { key: '_getContainerNode', value: function _getContainerNode(props) { var container = props.container; if (container) { return ReactDOM.findDOMNode(typeof container === 'function' ? container() : container); } else { return document.body; } } }, { key: '_measureCell', value: function _measureCell(_ref3) { var _ref3$clientHeight = _ref3.clientHeight; var clientHeight = _ref3$clientHeight === undefined ? false : _ref3$clientHeight; var _ref3$clientWidth = _ref3.clientWidth; var clientWidth = _ref3$clientWidth === undefined ? true : _ref3$clientWidth; var columnIndex = _ref3.columnIndex; var rowIndex = _ref3.rowIndex; var cellRenderer = this.props.cellRenderer; var rendered = cellRenderer({ columnIndex: columnIndex, rowIndex: rowIndex }); // Handle edge case where this method is called before the CellMeasurer has completed its initial render (and mounted). this._renderAndMount(); // @TODO Keep an eye on this for future React updates as the interface may change: // https://twitter.com/soprano/status/737316379712331776 ReactDOM.unstable_renderSubtreeIntoContainer(this, rendered, this._div); var measurements = { height: clientHeight && this._div.clientHeight, width: clientWidth && this._div.clientWidth }; ReactDOM.unmountComponentAtNode(this._div); return measurements; } }, { key: '_renderAndMount', value: function _renderAndMount() { if (!this._div) { this._div = document.createElement('div'); this._div.style.display = 'inline-block'; this._div.style.position = 'absolute'; this._div.style.visibility = 'hidden'; this._div.style.zIndex = -1; this._updateDivDimensions(this.props); this._containerNode = this._getContainerNode(this.props); this._containerNode.appendChild(this._div); } } }, { key: '_unmountContainer', value: function _unmountContainer() { if (this._div) { this._containerNode.removeChild(this._div); this._div = null; } this._containerNode = null; } }, { key: '_updateDivDimensions', value: function _updateDivDimensions(props) { var height = props.height; var width = props.width; if (height && height !== this._divHeight) { this._divHeight = height; this._div.style.height = height + 'px'; } if (width && width !== this._divWidth) { this._divWidth = width; this._div.style.width = width + 'px'; } } }]); return CellMeasurer; }(Component); CellMeasurer.propTypes = { /** * Renders a cell given its indices. * Should implement the following interface: ({ columnIndex: number, rowIndex: number }): PropTypes.node */ cellRenderer: PropTypes.func.isRequired, /** * Optional, custom caching strategy for cell sizes. */ cellSizeCache: PropTypes.object, /** * Function respondible for rendering a virtualized component. * This function should implement the following signature: * ({ getColumnWidth, getRowHeight, resetMeasurements }) => PropTypes.element */ children: PropTypes.func.isRequired, /** * Number of columns in grid. */ columnCount: PropTypes.number.isRequired, /** * A Node, Component instance, or function that returns either. * If this property is not specified the document body will be used. */ container: React.PropTypes.oneOfType([React.PropTypes.func, React.PropTypes.node]), /** * Assign a fixed :height in order to measure dynamic text :width only. */ height: PropTypes.number, /** * Number of rows in grid. */ rowCount: PropTypes.number.isRequired, /** * Assign a fixed :width in order to measure dynamic text :height only. */ width: PropTypes.number }; export default CellMeasurer;