react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
313 lines (243 loc) • 10.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactAddonsShallowCompare = require('react-addons-shallow-compare');
var _reactAddonsShallowCompare2 = _interopRequireDefault(_reactAddonsShallowCompare);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _defaultCellSizeCache = require('./defaultCellSizeCache');
var _defaultCellSizeCache2 = _interopRequireDefault(_defaultCellSizeCache);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/**
* 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 _defaultCellSizeCache2.default();
_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 (0, _reactAddonsShallowCompare2.default)(this, nextProps, nextState);
}
}, {
key: '_getContainerNode',
value: function _getContainerNode(props) {
var container = props.container;
if (container) {
return _reactDom2.default.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
_reactDom2.default.unstable_renderSubtreeIntoContainer(this, rendered, this._div);
var measurements = {
height: clientHeight && this._div.clientHeight,
width: clientWidth && this._div.clientWidth
};
_reactDom2.default.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;
}(_react.Component);
CellMeasurer.propTypes = {
/**
* Renders a cell given its indices.
* Should implement the following interface: ({ columnIndex: number, rowIndex: number }): PropTypes.node
*/
cellRenderer: _react.PropTypes.func.isRequired,
/**
* Optional, custom caching strategy for cell sizes.
*/
cellSizeCache: _react.PropTypes.object,
/**
* Function respondible for rendering a virtualized component.
* This function should implement the following signature:
* ({ getColumnWidth, getRowHeight, resetMeasurements }) => PropTypes.element
*/
children: _react.PropTypes.func.isRequired,
/**
* Number of columns in grid.
*/
columnCount: _react.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: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.func, _react2.default.PropTypes.node]),
/**
* Assign a fixed :height in order to measure dynamic text :width only.
*/
height: _react.PropTypes.number,
/**
* Number of rows in grid.
*/
rowCount: _react.PropTypes.number.isRequired,
/**
* Assign a fixed :width in order to measure dynamic text :height only.
*/
width: _react.PropTypes.number
};
exports.default = CellMeasurer;